From 272851e3708e17171be128cf5b960aeaeb6c36d8 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 Mar 2023 12:57:32 -0400 Subject: [PATCH 01/26] Expose StateDeltas to EvalTracer --- data/transactions/logic/debugger.go | 2 +- data/transactions/logic/eval.go | 2 +- data/transactions/logic/mocktracer/tracer.go | 2 +- data/transactions/logic/tracer.go | 18 +++++++++++++++--- ledger/internal/cow.go | 7 ++++++- ledger/internal/eval.go | 2 +- ledger/simulation/tracer.go | 6 +++--- ledger/simulation/tracer_test.go | 2 +- 8 files changed, 29 insertions(+), 12 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index ac2bcab7d7..e1c7eb78a0 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -73,7 +73,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) { } // AfterTxnGroup updates inner txn depth -func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, evalError error) { +func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, updateProvider UpdateProvider, evalError error) { a.txnDepth-- } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3de0f101d6..b2b06bbb8e 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5212,7 +5212,7 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxnGroup(ep) // Ensure we update the tracer before exiting defer func() { - ep.Tracer.AfterTxnGroup(ep, err) + ep.Tracer.AfterTxnGroup(ep, nil, err) }() } diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 2428d022e9..113d67fdba 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -137,7 +137,7 @@ func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { } // AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method -func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { +func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.UpdateProvider, evalError error) { d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), evalError != nil)) } diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index 89802b1c0e..7b504f6e2e 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -16,7 +16,14 @@ package logic -import "github.com/algorand/go-algorand/data/transactions" +import ( + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" +) + +type UpdateProvider interface { + Updates() ledgercore.StateDelta +} // EvalTracer functions are called by eval function during AVM program execution, if a tracer // is provided. @@ -105,7 +112,11 @@ type EvalTracer interface { // 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, evalError error) + // + // For top-level transaction groups, the updateProvider argument is an interface which can + // return the ledgercore.StateDelta updates that occurred because of this transaction group. For + // inner transaction groups, this argument is nil. + AfterTxnGroup(ep *EvalParams, updateProvider UpdateProvider, evalError error) // BeforeTxn is called before a transaction is executed. // @@ -139,7 +150,8 @@ type NullEvalTracer struct{} func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} // AfterTxnGroup does nothing -func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, evalError error) {} +func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, updateProvider UpdateProvider, evalError error) { +} // BeforeTxn does nothing func (n NullEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) {} diff --git a/ledger/internal/cow.go b/ledger/internal/cow.go index ade61f8204..bd077929e4 100644 --- a/ledger/internal/cow.go +++ b/ledger/internal/cow.go @@ -123,7 +123,7 @@ func makeRoundCowState(b roundCowParent, hdr bookkeeping.BlockHeader, proto conf return &cb } -func (cb *roundCowState) deltas() ledgercore.StateDelta { +func (cb *roundCowState) Updates() ledgercore.StateDelta { // Apply storage deltas to account deltas for addr, smap := range cb.sdeltas { for aapp, storeDelta := range smap { @@ -132,6 +132,11 @@ func (cb *roundCowState) deltas() ledgercore.StateDelta { } } } + return cb.mods +} + +func (cb *roundCowState) deltas() ledgercore.StateDelta { + cb.Updates() // Populate old values by looking through parent for key, value := range cb.mods.KvMods { diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index 2d184e4b4c..47c8e2c411 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -954,7 +954,7 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit eval.Tracer.BeforeTxnGroup(evalParams) // Ensure we update the tracer before exiting defer func() { - eval.Tracer.AfterTxnGroup(evalParams, err) + eval.Tracer.AfterTxnGroup(evalParams, cow, err) }() } diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 9a42e617c2..71122d9ba4 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -47,7 +47,7 @@ func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, a tracer.previousInnerTxns = tracer.previousInnerTxns[:len(tracer.previousInnerTxns)-1] } -func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { +func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.UpdateProvider, evalError error) { top := len(tracer.relativeCursor) - 1 if len(tracer.previousInnerTxns) != 0 { tracer.previousInnerTxns[len(tracer.previousInnerTxns)-1] += tracer.relativeCursor[top] + 1 @@ -143,9 +143,9 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { } } -func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { +func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.UpdateProvider, evalError error) { tracer.handleError(evalError) - tracer.cursorEvalTracer.AfterTxnGroup(ep, evalError) + tracer.cursorEvalTracer.AfterTxnGroup(ep, updateProvider, evalError) } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index b4429be414..06a31fc94b 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -236,7 +236,7 @@ func TestCursorEvalTracer(t *testing.T) { case mocktracer.BeforeTxnGroupEvent: tracer.BeforeTxnGroup(&ep) case mocktracer.AfterTxnGroupEvent: - tracer.AfterTxnGroup(&ep, nil) + tracer.AfterTxnGroup(&ep, nil, nil) default: t.Fatalf("unexpected timeline hook: %v", step.action) } From dcbe0e928f0b82973614d167ee705b1cc71e5e10 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 Mar 2023 13:00:03 -0400 Subject: [PATCH 02/26] doc --- ledger/internal/cow.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ledger/internal/cow.go b/ledger/internal/cow.go index bd077929e4..f5df56daf0 100644 --- a/ledger/internal/cow.go +++ b/ledger/internal/cow.go @@ -123,6 +123,8 @@ func makeRoundCowState(b roundCowParent, hdr bookkeeping.BlockHeader, proto conf return &cb } +// Updates returns a ledgercore.StateDelta containing all the changes made to this roundCowState. +// However, it does not return old state information (i.e. cb.mods.KvMods[key].OldValue). func (cb *roundCowState) Updates() ledgercore.StateDelta { // Apply storage deltas to account deltas for addr, smap := range cb.sdeltas { From 2080a1bd48295159effceb4fe311fe8fbb544a5d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 Mar 2023 14:35:08 -0400 Subject: [PATCH 03/26] Rename + doc --- data/transactions/logic/debugger.go | 2 +- data/transactions/logic/mocktracer/tracer.go | 2 +- data/transactions/logic/tracer.go | 7 ++++--- ledger/simulation/tracer.go | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index e1c7eb78a0..2d5c45451d 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -73,7 +73,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) { } // AfterTxnGroup updates inner txn depth -func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, updateProvider UpdateProvider, evalError error) { +func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, updateProvider LedgerUpdateProvider, evalError error) { a.txnDepth-- } diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 113d67fdba..a4075808b0 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -137,7 +137,7 @@ func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { } // AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method -func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.UpdateProvider, evalError error) { +func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.LedgerUpdateProvider, evalError error) { d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), evalError != nil)) } diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index 7b504f6e2e..ee0eddd14f 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -21,7 +21,8 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" ) -type UpdateProvider interface { +// LedgerUpdateProvider provides access to a ledgercore.StateDelta for a given transaction group. +type LedgerUpdateProvider interface { Updates() ledgercore.StateDelta } @@ -116,7 +117,7 @@ type EvalTracer interface { // For top-level transaction groups, the updateProvider argument is an interface which can // return the ledgercore.StateDelta updates that occurred because of this transaction group. For // inner transaction groups, this argument is nil. - AfterTxnGroup(ep *EvalParams, updateProvider UpdateProvider, evalError error) + AfterTxnGroup(ep *EvalParams, updateProvider LedgerUpdateProvider, evalError error) // BeforeTxn is called before a transaction is executed. // @@ -150,7 +151,7 @@ type NullEvalTracer struct{} func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} // AfterTxnGroup does nothing -func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, updateProvider UpdateProvider, evalError error) { +func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, updateProvider LedgerUpdateProvider, evalError error) { } // BeforeTxn does nothing diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 71122d9ba4..6e1ded5e5f 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -47,7 +47,7 @@ func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, a tracer.previousInnerTxns = tracer.previousInnerTxns[:len(tracer.previousInnerTxns)-1] } -func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.UpdateProvider, evalError error) { +func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.LedgerUpdateProvider, evalError error) { top := len(tracer.relativeCursor) - 1 if len(tracer.previousInnerTxns) != 0 { tracer.previousInnerTxns[len(tracer.previousInnerTxns)-1] += tracer.relativeCursor[top] + 1 @@ -143,7 +143,7 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { } } -func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.UpdateProvider, evalError error) { +func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.LedgerUpdateProvider, evalError error) { tracer.handleError(evalError) tracer.cursorEvalTracer.AfterTxnGroup(ep, updateProvider, evalError) } From bd327a4770db986a34d4624d2d606aee305a30d6 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 Mar 2023 16:28:59 -0400 Subject: [PATCH 04/26] Pass StateDelta directly, and add option to gather StateDeltas for individual txns --- data/transactions/logic/debugger.go | 3 +- data/transactions/logic/eval.go | 11 +++-- data/transactions/logic/ledger_test.go | 20 ++++----- data/transactions/logic/mocktracer/tracer.go | 5 ++- data/transactions/logic/tracer.go | 22 +++++----- ledger/internal/applications.go | 45 +++++++++++++------- ledger/internal/eval.go | 33 ++++++++++++-- ledger/simulation/tracer.go | 13 +++--- ledger/simulation/tracer_test.go | 2 +- 9 files changed, 100 insertions(+), 54 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 2d5c45451d..7bba10d3b8 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -29,6 +29,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" ) @@ -73,7 +74,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) { } // AfterTxnGroup updates inner txn depth -func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, updateProvider LedgerUpdateProvider, evalError error) { +func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, update *ledgercore.StateDelta, evalError error) { a.txnDepth-- } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index b2b06bbb8e..c3fb850a4a 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -238,7 +238,7 @@ type LedgerForLogic interface { SetBox(appIdx basics.AppIndex, key string, value []byte) error DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) (bool, error) - Perform(gi int, ep *EvalParams) error + Perform(gi int, ep *EvalParams) (*ledgercore.StateDelta, error) Counter() uint64 } @@ -285,6 +285,10 @@ type EvalParams struct { // optional tracer Tracer EvalTracer + // GranularEval controls whether we should create a child cow for each inner transaction. + // It does not make sense to enable this without also providing a Tracer. + GranularEval bool + // MinAvmVersion is the minimum allowed AVM version of this program. // The program must reject if its version is less than this version. If // MinAvmVersion is nil, we will compute it ourselves @@ -462,6 +466,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) SigLedger: caller.SigLedger, Ledger: caller.Ledger, Tracer: caller.Tracer, + GranularEval: caller.GranularEval, MinAvmVersion: &minAvmVersion, FeeCredit: caller.FeeCredit, Specials: caller.Specials, @@ -5221,10 +5226,10 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxn(ep, i) } - err := cx.Ledger.Perform(i, ep) + update, err := cx.Ledger.Perform(i, ep) if ep.Tracer != nil { - ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, err) + ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, update, err) } if err != nil { diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index ecda755627..e7e531e940 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -870,33 +870,33 @@ func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnF } // Perform causes txn to "occur" against the ledger. -func (l *Ledger) Perform(gi int, ep *EvalParams) error { +func (l *Ledger) Perform(gi int, ep *EvalParams) (*ledgercore.StateDelta, error) { txn := &ep.TxnGroup[gi] err := l.move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee.Raw) if err != nil { - return err + return nil, err } err = l.rekey(&txn.Txn) if err != nil { - return err + return nil, err } switch txn.Txn.Type { case protocol.PaymentTx: - return l.pay(txn.Txn.Sender, txn.Txn.PaymentTxnFields) + return nil, l.pay(txn.Txn.Sender, txn.Txn.PaymentTxnFields) case protocol.AssetTransferTx: - return l.axfer(txn.Txn.Sender, txn.Txn.AssetTransferTxnFields) + return nil, l.axfer(txn.Txn.Sender, txn.Txn.AssetTransferTxnFields) case protocol.AssetConfigTx: - return l.acfg(txn.Txn.Sender, txn.Txn.AssetConfigTxnFields, &txn.ApplyData) + return nil, l.acfg(txn.Txn.Sender, txn.Txn.AssetConfigTxnFields, &txn.ApplyData) case protocol.AssetFreezeTx: - return l.afrz(txn.Txn.Sender, txn.Txn.AssetFreezeTxnFields) + return nil, l.afrz(txn.Txn.Sender, txn.Txn.AssetFreezeTxnFields) case protocol.ApplicationCallTx: - return l.appl(txn.Txn.Sender, txn.Txn.ApplicationCallTxnFields, &txn.ApplyData, gi, ep) + return nil, l.appl(txn.Txn.Sender, txn.Txn.ApplicationCallTxnFields, &txn.ApplyData, gi, ep) case protocol.KeyRegistrationTx: - return nil // For now, presume success in test ledger + return nil, nil // For now, presume success in test ledger default: - return fmt.Errorf("%s txn in AVM", txn.Txn.Type) + return nil, fmt.Errorf("%s txn in AVM", txn.Txn.Type) } } diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index a4075808b0..649a4ed8b5 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -19,6 +19,7 @@ package mocktracer import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" ) @@ -137,7 +138,7 @@ func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { } // AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method -func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.LedgerUpdateProvider, evalError error) { +func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, update *ledgercore.StateDelta, evalError error) { d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), evalError != nil)) } @@ -147,7 +148,7 @@ func (d *Tracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { } // AfterTxn mocks the logic.EvalTracer.AfterTxn method -func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { +func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad, evalError != nil)) } diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index ee0eddd14f..b4a74b234f 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -21,11 +21,6 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" ) -// LedgerUpdateProvider provides access to a ledgercore.StateDelta for a given transaction group. -type LedgerUpdateProvider interface { - Updates() ledgercore.StateDelta -} - // EvalTracer functions are called by eval function during AVM program execution, if a tracer // is provided. // @@ -114,10 +109,10 @@ type EvalTracer interface { // 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. // - // For top-level transaction groups, the updateProvider argument is an interface which can - // return the ledgercore.StateDelta updates that occurred because of this transaction group. For - // inner transaction groups, this argument is nil. - AfterTxnGroup(ep *EvalParams, updateProvider LedgerUpdateProvider, evalError error) + // For top-level transaction groups, the update argument is the ledgercore.StateDelta updates + // that occurred because of this transaction group. For inner transaction groups, this argument + // is nil. + AfterTxnGroup(ep *EvalParams, update *ledgercore.StateDelta, evalError error) // BeforeTxn is called before a transaction is executed. // @@ -129,7 +124,10 @@ type EvalTracer interface { // 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, evalError error) + // + // TODO: if granular eval is enabled, update is the ledgercore.StateDelta updates that occurred + // because of this transaction. Otherwise, this argument is nil. + AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) // BeforeProgram is called before an app or LogicSig program is evaluated. BeforeProgram(cx *EvalContext) @@ -151,14 +149,14 @@ type NullEvalTracer struct{} func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} // AfterTxnGroup does nothing -func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, updateProvider LedgerUpdateProvider, evalError error) { +func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, update *ledgercore.StateDelta, evalError error) { } // 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, evalError error) { +func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { } // BeforeProgram does nothing diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index f0466b7d4a..7508da7576 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -292,18 +292,25 @@ func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basi return true, cs.kvDel(fullKey) } -func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { +func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.StateDelta, error) { + cowForTxn := cs + + if ep.GranularEval { + cowForTxn = cs.child(1) + defer cowForTxn.recycle() + } + txn := &ep.TxnGroup[gi] // move fee to pool - err := cs.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) + err := cowForTxn.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) if err != nil { - return err + return nil, err } - err = apply.Rekey(cs, &txn.Txn) + err = apply.Rekey(cowForTxn, &txn.Txn) if err != nil { - return err + return nil, err } // compared to eval.transaction() it may seem strange that we @@ -319,31 +326,31 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { switch txn.Txn.Type { case protocol.PaymentTx: - err = apply.Payment(txn.Txn.PaymentTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) + err = apply.Payment(txn.Txn.PaymentTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData) case protocol.KeyRegistrationTx: - err = apply.Keyreg(txn.Txn.KeyregTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData, - cs.Round()) + err = apply.Keyreg(txn.Txn.KeyregTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData, + cowForTxn.Round()) case protocol.AssetConfigTx: - err = apply.AssetConfig(txn.Txn.AssetConfigTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData, - cs.Counter()) + err = apply.AssetConfig(txn.Txn.AssetConfigTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData, + cowForTxn.Counter()) case protocol.AssetTransferTx: - err = apply.AssetTransfer(txn.Txn.AssetTransferTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) + err = apply.AssetTransfer(txn.Txn.AssetTransferTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData) case protocol.AssetFreezeTx: - err = apply.AssetFreeze(txn.Txn.AssetFreezeTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) + err = apply.AssetFreeze(txn.Txn.AssetFreezeTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData) case protocol.ApplicationCallTx: - err = apply.ApplicationCall(txn.Txn.ApplicationCallTxnFields, txn.Txn.Header, cs, &txn.ApplyData, - gi, ep, cs.Counter()) + err = apply.ApplicationCall(txn.Txn.ApplicationCallTxnFields, txn.Txn.Header, cowForTxn, &txn.ApplyData, + gi, ep, cowForTxn.Counter()) default: err = fmt.Errorf("%s tx in AVM", txn.Txn.Type) } if err != nil { - return err + return nil, err } // We don't check min balances during in app txns. @@ -352,5 +359,11 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { // top-level txn concludes, because cow will return all changed accounts in // modifiedAccounts(). - return nil + if ep.GranularEval { + update := cowForTxn.Updates() + cowForTxn.commitToParent() + return &update, nil + } + + return nil, nil } diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index 47c8e2c411..5df3ccb32d 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -595,6 +595,10 @@ type BlockEvaluator struct { maxTxnBytesPerBlock int + // GranularEval controls whether the evaluator should create a child cow for each transaction. + // It does not make sense to enable this without also providing a Tracer. + GranularEval bool + Tracer logic.EvalTracer } @@ -948,13 +952,16 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit defer cow.recycle() evalParams := logic.NewEvalParams(txgroup, &eval.proto, &eval.specials) + evalParams.GranularEval = eval.GranularEval evalParams.Tracer = eval.Tracer if eval.Tracer != nil { eval.Tracer.BeforeTxnGroup(evalParams) // Ensure we update the tracer before exiting defer func() { - eval.Tracer.AfterTxnGroup(evalParams, cow, err) + // TODO: is it ok to call .Updates() after .commitToParent()? + update := cow.Updates() + eval.Tracer.AfterTxnGroup(evalParams, &update, err) }() } @@ -963,14 +970,34 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock + cowForTxn := cow + + if eval.GranularEval { + cowForTxn = cow.child(1) + } + if eval.Tracer != nil { eval.Tracer.BeforeTxn(evalParams, gi) } - err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cow, &txib) + err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cowForTxn, &txib) if eval.Tracer != nil { - eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, err) + var update *ledgercore.StateDelta + if eval.GranularEval { + // Only include if we're sure the cowForTxn contains ONLY the updates from this txn + u := cowForTxn.Updates() + update = &u + } + eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, update, err) + } + + if eval.GranularEval { + cowForTxn.commitToParent() + // It would be nice if we could reference the same cow for all txns without having + // to put into and take out of the sync.Pool, but cow.child MUST retrieve it from the + // sync pool as of right now. + cowForTxn.recycle() } if err != nil { diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 6e1ded5e5f..fb703c9091 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -22,6 +22,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" ) // cursorEvalTracer is responsible for maintaining a TxnPath that points to the currently executing @@ -43,11 +44,11 @@ func (tracer *cursorEvalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) tracer.previousInnerTxns = append(tracer.previousInnerTxns, 0) } -func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { +func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { tracer.previousInnerTxns = tracer.previousInnerTxns[:len(tracer.previousInnerTxns)-1] } -func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.LedgerUpdateProvider, evalError error) { +func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, update *ledgercore.StateDelta, evalError error) { top := len(tracer.relativeCursor) - 1 if len(tracer.previousInnerTxns) != 0 { tracer.previousInnerTxns[len(tracer.previousInnerTxns)-1] += tracer.relativeCursor[top] + 1 @@ -143,9 +144,9 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { } } -func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, updateProvider logic.LedgerUpdateProvider, evalError error) { +func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, update *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) - tracer.cursorEvalTracer.AfterTxnGroup(ep, updateProvider, evalError) + tracer.cursorEvalTracer.AfterTxnGroup(ep, update, evalError) } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { @@ -156,10 +157,10 @@ func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { applyDataOfCurrentTxn.EvalDelta = evalDelta } -func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { +func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) tracer.saveApplyData(ad) - tracer.cursorEvalTracer.AfterTxn(ep, groupIndex, ad, evalError) + tracer.cursorEvalTracer.AfterTxn(ep, groupIndex, ad, update, evalError) } func (tracer *evalTracer) saveEvalDelta(evalDelta transactions.EvalDelta, appIDToSave basics.AppIndex) { diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index 06a31fc94b..89c3faef5b 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -232,7 +232,7 @@ func TestCursorEvalTracer(t *testing.T) { case mocktracer.BeforeTxnEvent: tracer.BeforeTxn(&ep, groupIndex) case mocktracer.AfterTxnEvent: - tracer.AfterTxn(&ep, groupIndex, transactions.ApplyData{}, nil) + tracer.AfterTxn(&ep, groupIndex, transactions.ApplyData{}, nil, nil) case mocktracer.BeforeTxnGroupEvent: tracer.BeforeTxnGroup(&ep) case mocktracer.AfterTxnGroupEvent: From 04a8819b0b563a2a03748cb0c7ab25b1e0e9c1e9 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 Mar 2023 16:53:45 -0400 Subject: [PATCH 05/26] Commit to parent in granular eval even during errors, for idential behavior --- ledger/internal/applications.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 7508da7576..0f1b1ebb27 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -297,7 +297,10 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.Stat if ep.GranularEval { cowForTxn = cs.child(1) - defer cowForTxn.recycle() + defer func() { + cowForTxn.commitToParent() + cowForTxn.recycle() + }() } txn := &ep.TxnGroup[gi] @@ -361,7 +364,6 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.Stat if ep.GranularEval { update := cowForTxn.Updates() - cowForTxn.commitToParent() return &update, nil } From de0204837d99698d401590af837c5d1a7e8eddde Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 29 Mar 2023 12:51:56 -0400 Subject: [PATCH 06/26] Update docstring --- data/transactions/logic/tracer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index b4a74b234f..fc75513934 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -125,8 +125,9 @@ type EvalTracer interface { // 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. // - // TODO: if granular eval is enabled, update is the ledgercore.StateDelta updates that occurred - // because of this transaction. Otherwise, this argument is nil. + // If GranularEval is enabled by the evaluator (specified either in logic.EvalParams or ledger's + // BlockEvaluator), update is the ledgercore.StateDelta updates that occurred because of this + // transaction. Otherwise, this argument is nil. AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) // BeforeProgram is called before an app or LogicSig program is evaluated. From bfcbe93bab95ffce50a97817a13559153f2d7555 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 29 Mar 2023 16:29:32 -0400 Subject: [PATCH 07/26] Make updates fn private --- ledger/eval/applications.go | 2 +- ledger/eval/cow.go | 6 +++--- ledger/eval/eval.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index c80d9fa2c4..f59d99abe7 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -363,7 +363,7 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.Stat // modifiedAccounts(). if ep.GranularEval { - update := cowForTxn.Updates() + update := cowForTxn.updates() return &update, nil } diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 772016215b..7967a01189 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -123,9 +123,9 @@ func makeRoundCowState(b roundCowParent, hdr bookkeeping.BlockHeader, proto conf return &cb } -// Updates returns a ledgercore.StateDelta containing all the changes made to this roundCowState. +// updates returns a ledgercore.StateDelta containing all the changes made to this roundCowState. // However, it does not return old state information (i.e. cb.mods.KvMods[key].OldValue). -func (cb *roundCowState) Updates() ledgercore.StateDelta { +func (cb *roundCowState) updates() ledgercore.StateDelta { // Apply storage deltas to account deltas for addr, smap := range cb.sdeltas { for aapp, storeDelta := range smap { @@ -138,7 +138,7 @@ func (cb *roundCowState) Updates() ledgercore.StateDelta { } func (cb *roundCowState) deltas() ledgercore.StateDelta { - cb.Updates() + cb.updates() // Populate old values by looking through parent for key, value := range cb.mods.KvMods { diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index e6def1b10a..28d3567a6e 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -959,8 +959,8 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit eval.Tracer.BeforeTxnGroup(evalParams) // Ensure we update the tracer before exiting defer func() { - // TODO: is it ok to call .Updates() after .commitToParent()? - update := cow.Updates() + // TODO: need to check, is it ok to call .updates() after .commitToParent()? + update := cow.updates() eval.Tracer.AfterTxnGroup(evalParams, &update, err) }() } @@ -986,7 +986,7 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit var update *ledgercore.StateDelta if eval.GranularEval { // Only include if we're sure the cowForTxn contains ONLY the updates from this txn - u := cowForTxn.Updates() + u := cowForTxn.updates() update = &u } eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, update, err) From c07ccd24571a2a8819895d0dd4eb202f7af9df4c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 31 Mar 2023 14:41:32 -0700 Subject: [PATCH 08/26] Reuse child cows in BlockEvaluator.TransactionGroup & remove Updates function --- data/transactions/logic/debugger.go | 2 +- data/transactions/logic/eval.go | 5 +++-- data/transactions/logic/tracer.go | 12 +++++----- ledger/eval/applications.go | 23 +++++++++---------- ledger/eval/cow.go | 18 +++++++-------- ledger/eval/cow_test.go | 11 +++++++++ ledger/eval/eval.go | 35 ++++++++++++++++------------- ledger/simulation/tracer.go | 12 +++++----- 8 files changed, 65 insertions(+), 53 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 7bba10d3b8..0d62b8dc84 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -74,7 +74,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) { } // AfterTxnGroup updates inner txn depth -func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, update *ledgercore.StateDelta, evalError error) { +func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDelta, evalError error) { a.txnDepth-- } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index c3fb850a4a..42bf76e322 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -285,8 +285,9 @@ type EvalParams struct { // optional tracer Tracer EvalTracer - // GranularEval controls whether we should create a child cow for each inner transaction. - // It does not make sense to enable this without also providing a Tracer. + // GranularEval controls whether we should create a child cow for each inner transaction. This + // is purely for debugging/tracing purposes, since this should have no effect on the actual + // result. GranularEval bool // MinAvmVersion is the minimum allowed AVM version of this program. diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index fc75513934..b32dae689b 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -109,10 +109,10 @@ type EvalTracer interface { // 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. // - // For top-level transaction groups, the update argument is the ledgercore.StateDelta updates + // For top-level transaction groups, the deltas argument is the ledgercore.StateDelta changes // that occurred because of this transaction group. For inner transaction groups, this argument // is nil. - AfterTxnGroup(ep *EvalParams, update *ledgercore.StateDelta, evalError error) + AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDelta, evalError error) // BeforeTxn is called before a transaction is executed. // @@ -126,9 +126,9 @@ type EvalTracer interface { // ep.TxnGroup[groupIndex].ApplyData, since it may not be populated at this point. // // If GranularEval is enabled by the evaluator (specified either in logic.EvalParams or ledger's - // BlockEvaluator), update is the ledgercore.StateDelta updates that occurred because of this + // BlockEvaluator), deltas is the ledgercore.StateDelta changes that occurred because of this // transaction. Otherwise, this argument is nil. - AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) + AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) // BeforeProgram is called before an app or LogicSig program is evaluated. BeforeProgram(cx *EvalContext) @@ -150,14 +150,14 @@ type NullEvalTracer struct{} func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} // AfterTxnGroup does nothing -func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, update *ledgercore.StateDelta, evalError error) { +func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDelta, evalError error) { } // 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, update *ledgercore.StateDelta, evalError error) { +func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { } // BeforeProgram does nothing diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index f59d99abe7..c3366763c2 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -292,12 +292,17 @@ func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basi return true, cs.kvDel(fullKey) } -func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.StateDelta, error) { +func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgercore.StateDelta, err error) { cowForTxn := cs if ep.GranularEval { + // In theory we could reuse this child cow allocation for all of the transactions in this + // group; that's what BlockEvaluator.TransactionGroup does. However, achieving the same + // thing here would require interface changes. cowForTxn = cs.child(1) defer func() { + d := cowForTxn.deltas() + deltas = &d cowForTxn.commitToParent() cowForTxn.recycle() }() @@ -306,14 +311,14 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.Stat txn := &ep.TxnGroup[gi] // move fee to pool - err := cowForTxn.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) + err = cowForTxn.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) if err != nil { - return nil, err + return } err = apply.Rekey(cowForTxn, &txn.Txn) if err != nil { - return nil, err + return } // compared to eval.transaction() it may seem strange that we @@ -352,9 +357,6 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.Stat default: err = fmt.Errorf("%s tx in AVM", txn.Txn.Type) } - if err != nil { - return nil, err - } // We don't check min balances during in app txns. @@ -362,10 +364,5 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (*ledgercore.Stat // top-level txn concludes, because cow will return all changed accounts in // modifiedAccounts(). - if ep.GranularEval { - update := cowForTxn.updates() - return &update, nil - } - - return nil, nil + return } diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 19a212bf2e..d295862c37 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -123,9 +123,7 @@ func makeRoundCowState(b roundCowParent, hdr bookkeeping.BlockHeader, proto conf return &cb } -// updates returns a ledgercore.StateDelta containing all the changes made to this roundCowState. -// However, it does not return old state information (i.e. cb.mods.KvMods[key].OldValue). -func (cb *roundCowState) updates() ledgercore.StateDelta { +func (cb *roundCowState) deltas() ledgercore.StateDelta { // Apply storage deltas to account deltas for addr, smap := range cb.sdeltas { for aapp, storeDelta := range smap { @@ -134,11 +132,6 @@ func (cb *roundCowState) updates() ledgercore.StateDelta { } } } - return cb.mods -} - -func (cb *roundCowState) deltas() ledgercore.StateDelta { - cb.updates() // Populate old values by looking through parent for key, value := range cb.mods.KvMods { @@ -278,6 +271,14 @@ func (cb *roundCowState) SetStateProofNextRound(rnd basics.Round) { func (cb *roundCowState) child(hint int) *roundCowState { ch := childPool.Get().(*roundCowState) + cb.reuseChild(ch, hint) + return ch +} + +// reuseChild creates a new child of this roundCowState, reusing the provided child object. If the +// child object was previously used, the caller MUST ensure ch.reset() is called before invoking +// this function. +func (cb *roundCowState) reuseChild(ch *roundCowState, hint int) { ch.lookupParent = cb ch.commitParent = cb ch.proto = cb.proto @@ -293,7 +294,6 @@ func (cb *roundCowState) child(hint int) *roundCowState { ch.compatibilityGetKeyCache = make(map[basics.Address]map[storagePtr]uint64) } } - return ch } func (cb *roundCowState) commitToParent() { diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index f5e4fe25af..c57417818f 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -201,6 +201,17 @@ func BenchmarkCowChild(b *testing.B) { } } +func BenchmarkCowReuseChild(b *testing.B) { + b.ReportAllocs() + cow := makeRoundCowState(nil, bookkeeping.BlockHeader{}, config.ConsensusParams{}, 10000, ledgercore.AccountTotals{}, 16) + calf := cow.child(1) + calf.reset() + for i := 0; i < b.N; i++ { + cow.reuseChild(calf, 16) + calf.reset() + } +} + // Ideally we'd be able to randomize the roundCowState but can't do it via reflection // since it' can't set unexported fields. This test just makes sure that all of the existing // fields are correctly reset but won't be able to catch any new fields added. diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 8ae3e94be1..6978326469 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -601,7 +601,8 @@ type BlockEvaluator struct { maxTxnBytesPerBlock int // GranularEval controls whether the evaluator should create a child cow for each transaction. - // It does not make sense to enable this without also providing a Tracer. + // This is purely for debugging/tracing purposes, since this should have no effect on the actual + // result. GranularEval bool Tracer logic.EvalTracer @@ -964,21 +965,27 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit eval.Tracer.BeforeTxnGroup(evalParams) // Ensure we update the tracer before exiting defer func() { - // TODO: need to check, is it ok to call .updates() after .commitToParent()? - update := cow.updates() - eval.Tracer.AfterTxnGroup(evalParams, &update, err) + // TODO: need to check, is it ok to call .deltas() after .commitToParent()? + deltas := cow.deltas() + eval.Tracer.AfterTxnGroup(evalParams, &deltas, err) }() } // Evaluate each transaction in the group txibs = make([]transactions.SignedTxnInBlock, 0, len(txgroup)) + cowForTxn := cow for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock - cowForTxn := cow - if eval.GranularEval { - cowForTxn = cow.child(1) + if gi == 0 { + cowForTxn = cow.child(1) + defer cowForTxn.recycle() + } else { + // Reuse the same cow allocation for all transactions in this group + cowForTxn.reset() + cow.reuseChild(cowForTxn, 1) + } } if eval.Tracer != nil { @@ -988,21 +995,17 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cowForTxn, &txib) if eval.Tracer != nil { - var update *ledgercore.StateDelta + var deltas *ledgercore.StateDelta if eval.GranularEval { - // Only include if we're sure the cowForTxn contains ONLY the updates from this txn - u := cowForTxn.updates() - update = &u + // Only include if we're sure the cowForTxn contains ONLY the deltas from this txn + d := cowForTxn.deltas() + deltas = &d } - eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, update, err) + eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, deltas, err) } if eval.GranularEval { cowForTxn.commitToParent() - // It would be nice if we could reference the same cow for all txns without having - // to put into and take out of the sync.Pool, but cow.child MUST retrieve it from the - // sync pool as of right now. - cowForTxn.recycle() } if err != nil { diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index fb703c9091..76fa4912fc 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -44,11 +44,11 @@ func (tracer *cursorEvalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) tracer.previousInnerTxns = append(tracer.previousInnerTxns, 0) } -func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { +func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { tracer.previousInnerTxns = tracer.previousInnerTxns[:len(tracer.previousInnerTxns)-1] } -func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, update *ledgercore.StateDelta, evalError error) { +func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { top := len(tracer.relativeCursor) - 1 if len(tracer.previousInnerTxns) != 0 { tracer.previousInnerTxns[len(tracer.previousInnerTxns)-1] += tracer.relativeCursor[top] + 1 @@ -144,9 +144,9 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { } } -func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, update *ledgercore.StateDelta, evalError error) { +func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) - tracer.cursorEvalTracer.AfterTxnGroup(ep, update, evalError) + tracer.cursorEvalTracer.AfterTxnGroup(ep, deltas, evalError) } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { @@ -157,10 +157,10 @@ func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { applyDataOfCurrentTxn.EvalDelta = evalDelta } -func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { +func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) tracer.saveApplyData(ad) - tracer.cursorEvalTracer.AfterTxn(ep, groupIndex, ad, update, evalError) + tracer.cursorEvalTracer.AfterTxn(ep, groupIndex, ad, deltas, evalError) } func (tracer *evalTracer) saveEvalDelta(evalDelta transactions.EvalDelta, appIDToSave basics.AppIndex) { From 2801129e3150b7b4a8cb28091f15ade99cb6a338 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 14 Apr 2023 10:20:23 -0700 Subject: [PATCH 09/26] WIP --- .../logic/mocktracer/scenarios.go | 118 +++++++++++++----- data/transactions/logic/mocktracer/tracer.go | 34 +++-- ledger/eval/eval_test.go | 89 ++++++++++--- ledger/simulation/simulation_eval_test.go | 25 ++++ ledger/simulation/simulator_test.go | 6 +- ledger/simulation/trace.go | 10 +- ledger/simulation/tracer.go | 4 + 7 files changed, 223 insertions(+), 63 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 89fbdafe9c..b6edcdf935 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -26,6 +26,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" ) @@ -78,9 +79,13 @@ func fillProgramTemplate(beforeInnersOps, innerApprovalProgram, betweenInnersOps // TestScenarioInfo holds arguments used to call a TestScenarioGenerator type TestScenarioInfo struct { - CallingTxn transactions.Transaction - MinFee basics.MicroAlgos - CreatedAppID basics.AppIndex + CallingTxn transactions.Transaction + SenderData basics.AccountData + AppAccountData basics.AccountData + FeeSinkData basics.AccountData + FeeSinkAddr basics.Address + MinFee basics.MicroAlgos + CreatedAppID basics.AppIndex } func expectedApplyData(info TestScenarioInfo) transactions.ApplyData { @@ -172,6 +177,7 @@ type TestScenario struct { FailedAt []uint64 ExpectedEvents []Event ExpectedSimulationAD transactions.ApplyData + ExpectedStateDelta ledgercore.StateDelta AppBudgetAdded uint64 AppBudgetConsumed uint64 TxnAppBudgetConsumed []uint64 @@ -201,6 +207,51 @@ func GetTestScenarios() map[string]TestScenarioGenerator { noFailure := func(info TestScenarioInfo) TestScenario { expectedAD := expectedApplyData(info) program := fillProgramTemplate("", successInnerProgram, "", 1, 2, "pushint 1") + ops, err := logic.AssembleString(program) + if err != nil { + panic(err) + } + + expectedSenderData := ledgercore.ToAccountData(info.SenderData) + expectedSenderData.MicroAlgos.Raw -= info.CallingTxn.Fee.Raw + expectedSenderData.TotalAppParams += 1 + expectedAppAccountData := ledgercore.ToAccountData(info.AppAccountData) + expectedAppAccountData.MicroAlgos.Raw -= 3 * info.MinFee.Raw + expectedAppAccountData.TotalAppParams += 1 + expectedFeeSinkData := ledgercore.ToAccountData(info.FeeSinkData) + expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw + 3*info.MinFee.Raw + + var expectedDelta ledgercore.StateDelta + expectedDelta.Accts.Upsert(info.CallingTxn.Sender, expectedSenderData) + expectedDelta.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) + expectedDelta.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + expectedDelta.Accts.UpsertAppResource(info.CallingTxn.Sender, info.CreatedAppID, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: ops.Program, + ClearStateProgram: info.CallingTxn.ClearStateProgram, + StateSchemas: basics.StateSchemas{ + LocalStateSchema: info.CallingTxn.LocalStateSchema, + GlobalStateSchema: info.CallingTxn.GlobalStateSchema, + }, + ExtraProgramPages: info.CallingTxn.ExtraProgramPages, + }, + }, ledgercore.AppLocalStateDelta{}) + expectedDelta.Accts.UpsertAppResource(info.CreatedAppID.Address(), info.CreatedAppID+1, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: successInnerProgramBytes, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, // #pragma version 6; int 1; + }, + }, ledgercore.AppLocalStateDelta{}) + expectedDelta.AddCreatable(basics.CreatableIndex(info.CreatedAppID), ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CallingTxn.Sender, + }) + expectedDelta.AddCreatable(basics.CreatableIndex(info.CreatedAppID+1), ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CreatedAppID.Address(), + }) return TestScenario{ Outcome: ApprovalOutcome, Program: program, @@ -221,8 +272,8 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, nil, false), + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -230,19 +281,20 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, false), + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, nil, false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, false), - AfterTxnGroup(2, false), // end second itxn group + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, nil, false), + AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedAD, false), + AfterTxn(protocol.ApplicationCallTx, expectedAD, nil, false), }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 35, TxnAppBudgetConsumed: []uint64{0, 35}, @@ -294,7 +346,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(4, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), }, }), ExpectedSimulationAD: expectedAD, @@ -342,11 +394,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, true), - AfterTxnGroup(1, true), // end first itxn group + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, nil, true), + AfterTxnGroup(1, nil, true), // end first itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), }, }), ExpectedSimulationAD: expectedAD, @@ -392,14 +444,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(4, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), }, }), ExpectedSimulationAD: expectedAD, @@ -447,8 +499,8 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -456,11 +508,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, true), - AfterTxnGroup(2, true), // end second itxn group + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, nil, true), + AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), }, }), ExpectedSimulationAD: expectedAD, @@ -509,8 +561,8 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -518,13 +570,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, false), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, nil, false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, true), - AfterTxnGroup(2, true), // end second itxn group + AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, nil, true), + AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), }, }), ExpectedSimulationAD: expectedAD, @@ -566,8 +618,8 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -575,16 +627,16 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, false), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, nil, false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, false), - AfterTxnGroup(2, false), // end second itxn group + AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, nil, false), + AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), }, }), ExpectedSimulationAD: expectedAD, diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 649a4ed8b5..4709265935 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -58,6 +58,9 @@ type Event struct { // only for AfterTxn TxnApplyData transactions.ApplyData + // only for AfterTxnGroup and AfterTxn + Deltas *ledgercore.StateDelta + // only for BeforeTxnGroup and AfterTxnGroup GroupSize int @@ -71,8 +74,8 @@ func BeforeTxnGroup(groupSize int) Event { } // AfterTxnGroup creates a new Event with the type AfterTxnGroupEvent -func AfterTxnGroup(groupSize int, hasError bool) Event { - return Event{Type: AfterTxnGroupEvent, GroupSize: groupSize, HasError: hasError} +func AfterTxnGroup(groupSize int, deltas *ledgercore.StateDelta, hasError bool) Event { + return Event{Type: AfterTxnGroupEvent, GroupSize: groupSize, Deltas: copyDeltas(deltas), HasError: hasError} } // BeforeProgram creates a new Event with the type BeforeProgramEvent @@ -86,8 +89,8 @@ func BeforeTxn(txnType protocol.TxType) Event { } // AfterTxn creates a new Event with the type AfterTxnEvent -func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData, hasError bool) Event { - return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad, HasError: hasError} +func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData, deltas *ledgercore.StateDelta, hasError bool) Event { + return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad, Deltas: copyDeltas(deltas), HasError: hasError} } // AfterProgram creates a new Event with the type AfterProgramEvent @@ -138,8 +141,8 @@ func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { } // AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method -func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, update *ledgercore.StateDelta, evalError error) { - d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), evalError != nil)) +func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { + d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), deltas, evalError != nil)) } // BeforeTxn mocks the logic.EvalTracer.BeforeTxn method @@ -148,8 +151,8 @@ func (d *Tracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { } // AfterTxn mocks the logic.EvalTracer.AfterTxn method -func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, update *ledgercore.StateDelta, evalError error) { - d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad, evalError != nil)) +func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { + d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad, deltas, evalError != nil)) } // BeforeProgram mocks the logic.EvalTracer.BeforeProgram method @@ -171,3 +174,18 @@ func (d *Tracer) BeforeOpcode(cx *logic.EvalContext) { func (d *Tracer) AfterOpcode(cx *logic.EvalContext, evalError error) { d.Events = append(d.Events, AfterOpcode(evalError != nil)) } + +// copyDeltas makes a deep copy of the given ledgercore.StateDelta pointer, if it's not nil. +// This is inefficient, but it should only be used for testing. +func copyDeltas(deltas *ledgercore.StateDelta) *ledgercore.StateDelta { + if deltas == nil { + return nil + } + encoded := protocol.EncodeReflect(deltas) + var clone ledgercore.StateDelta + err := protocol.DecodeReflect(encoded, &clone) + if err != nil { + panic(err) + } + return &clone +} diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 57860c1e92..97d8b83249 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -243,6 +243,11 @@ func TestTransactionGroupWithTracer(t *testing.T) { scenarios := mocktracer.GetTestScenarios() + // TODO: remove this filter + scenarios = map[string]mocktracer.TestScenarioGenerator{ + "none": scenarios["none"], + } + type tracerTestCase struct { name string firstTxnBehavior string @@ -250,7 +255,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { } var testCases []tracerTestCase - firstIteration := true + firstIteration := false // TODO: change back to true for scenarioName, scenario := range scenarios { firstTxnBehaviors := []string{"approve"} if firstIteration { @@ -281,7 +286,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) genesisBalances := bookkeeping.GenesisBalances{ - Balances: genesisInitState.Accounts, + Balances: balances, FeeSink: testSinkAddr, RewardsPool: testPoolAddr, Timestamp: 0, @@ -342,7 +347,7 @@ int 1`, // an app call with inner txn innerAppCallTxn := txntest.Txn{ Type: protocol.ApplicationCallTx, - Sender: addrs[0], + Sender: addrs[4], ClearStateProgram: `#pragma version 6 int 1`, @@ -351,10 +356,22 @@ int 1`, Fee: minFee, GenesisHash: genHash, } + + // TODO: account for fee sink data changes from prior txns + expectedFeeSinkData := balances[testSinkAddr] + expectedFeeSinkData.MicroAlgos.Raw += basicAppCallTxn.Amount + if testCase.firstTxnBehavior == "approve" { + expectedFeeSinkData.MicroAlgos.Raw += payTxn.Amount + } + scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ - CallingTxn: innerAppCallTxn.Txn(), - MinFee: minFee, - CreatedAppID: innerAppID, + CallingTxn: innerAppCallTxn.Txn(), + SenderData: balances[addrs[4]], + AppAccountData: balances[innerAppAddress], + FeeSinkData: expectedFeeSinkData, + FeeSinkAddr: testSinkAddr, + MinFee: minFee, + CreatedAppID: innerAppID, }) innerAppCallTxn.ApprovalProgram = scenario.Program @@ -363,7 +380,7 @@ int 1`, txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ basicAppCallTxn.Txn().Sign(keys[0]), payTxn.Txn().Sign(keys[1]), - innerAppCallTxn.Txn().Sign(keys[0]), + innerAppCallTxn.Txn().Sign(keys[4]), }) require.Len(t, eval.block.Payset, 0) @@ -403,8 +420,49 @@ int 1`, }, } + expectedAcct0Data := ledgercore.ToAccountData(balances[addrs[0]]) + expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw + expectedAcct0Data.TotalAppParams = 1 + + expectedDelta := ledgercore.MakeStateDelta(&newBlock.BlockHeader, blkHeader.TimeStamp, 0, 0) + expectedDelta.Accts.Upsert(addrs[0], expectedAcct0Data) + expectedDelta.Accts.Upsert(testSinkAddr, ledgercore.ToAccountData(expectedFeeSinkData)) + expectedDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: txgroup[0].Txn.ApprovalProgram, + ClearStateProgram: txgroup[0].Txn.ClearStateProgram, + }, + }, ledgercore.AppLocalStateDelta{}) + expectedDelta.AddCreatable(1, ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: addrs[0], + }) + for i, stxn := range txgroup { + expectedDelta.Txids[stxn.ID()] = ledgercore.IncludedTransactions{ + LastValid: stxn.Txn.LastValid, + Intra: uint64(i), + } + } + // expectedDelta = eval.state.deltas() + var expectedEvents []mocktracer.Event if testCase.firstTxnBehavior == "approve" { + expectedAcct1Data := ledgercore.AccountData{} + expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) + expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount + expectedAcct3Data := ledgercore.ToAccountData(balances[addrs[3]]) + expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw + expectedDelta.Accts.Upsert(addrs[1], expectedAcct1Data) + expectedDelta.Accts.Upsert(addrs[2], expectedAcct2Data) + expectedDelta.Accts.Upsert(addrs[3], expectedAcct3Data) + expectedDelta.Accts.MergeAccounts(scenario.ExpectedStateDelta.Accts) + for key, delta := range scenario.ExpectedStateDelta.KvMods { + expectedDelta.AddKvMod(key, delta) + } + for id, delta := range scenario.ExpectedStateDelta.Creatables { + expectedDelta.AddCreatable(id, delta) + } expectedEvents = mocktracer.FlattenEvents([][]mocktracer.Event{ { mocktracer.BeforeTxnGroup(3), @@ -414,13 +472,13 @@ int 1`, mocktracer.OpcodeEvents(3, false), { mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, false), // end basicAppCallTxn - mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn - mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, false), // end payTxn + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, nil, false), // end basicAppCallTxn + mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn + mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, nil, false), // end payTxn }, scenario.ExpectedEvents, { - mocktracer.AfterTxnGroup(3, scenario.Outcome != mocktracer.ApprovalOutcome), + mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), }, }) } else { @@ -436,12 +494,15 @@ int 1`, mocktracer.OpcodeEvents(3, hasError), { mocktracer.AfterProgram(logic.ModeApp, hasError), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, true), // end basicAppCallTxn - mocktracer.AfterTxnGroup(3, true), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, nil, true), // end basicAppCallTxn + mocktracer.AfterTxnGroup(3, &expectedDelta, true), }, }) } - require.Equal(t, expectedEvents, mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events)) + e := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) + require.Equal(t, len(expectedEvents), len(e)) + require.Equal(t, expectedEvents[len(expectedEvents)-1].Deltas, e[len(e)-1].Deltas) + require.Equal(t, expectedEvents, e) }) } } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 134fdd2339..1d95364126 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -30,6 +30,7 @@ import ( "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/ledger/simulation" simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -205,6 +206,12 @@ func TestPayTxn(t *testing.T) { txn = txn.Txn.Sign(sender.Sk) } + expectedSenderAcctData := sender.AcctData + expectedSenderAcctData.MicroAlgos.Raw -= txn.Txn.Fee.Raw + txn.Txn.Amount.Raw + + expectedReceiverAcctData := receiver.AcctData + expectedReceiverAcctData.MicroAlgos.Raw += txn.Txn.Amount.Raw + return simulationTestCase{ input: []transactions.SignedTxn{txn}, expected: simulation.Result{ @@ -217,6 +224,24 @@ func TestPayTxn(t *testing.T) { MissingSignature: !signed, }, }, + Delta: ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: sender.Addr, + AccountData: ledgercore.ToAccountData(expectedSenderAcctData), + }, + { + Addr: ledgertesting.SinkAddr(), + // AccountData: ledgercore.ToAccountData(ledgercore.AccountData{}), + }, + { + Addr: receiver.Addr, + AccountData: ledgercore.ToAccountData(expectedReceiverAcctData), + }, + }, + }, + }, }, }, WouldSucceed: signed, diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 76ae0ef1b2..1ca76a5002 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -154,14 +154,14 @@ int 1`, // Txn evaluation mocktracer.BeforeTxnGroup(2), mocktracer.BeforeTxn(protocol.PaymentTx), - mocktracer.AfterTxn(protocol.PaymentTx, payset[0].ApplyData, false), + mocktracer.AfterTxn(protocol.PaymentTx, payset[0].ApplyData, nil, false), mocktracer.BeforeTxn(protocol.ApplicationCallTx), mocktracer.BeforeProgram(logic.ModeApp), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, payset[1].ApplyData, false), - mocktracer.AfterTxnGroup(2, false), + mocktracer.AfterTxn(protocol.ApplicationCallTx, payset[1].ApplyData, nil, false), + mocktracer.AfterTxnGroup(2, nil, false), } require.Equal(t, expectedEvents, mockTracer.Events) } diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 6313e0539c..e6dde191bf 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -38,18 +38,18 @@ type TxnResult struct { // TxnGroupResult contains the simulation result for a single transaction group type TxnGroupResult struct { - Txns []TxnResult + Txns []TxnResult + // Delta is the state delta for this group. + Delta ledgercore.StateDelta + // FailureMessage will be the error message for the first transaction in the group which errors. + // If the group succeeds, this will be empty. FailureMessage string - // FailedAt is the path to the txn that failed inside of this group FailedAt TxnPath - // AppBudgetAdded is the total opcode budget for this group AppBudgetAdded uint64 - // AppBudgetConsumed is the total opcode cost used for this group AppBudgetConsumed uint64 - // FeeCredit is the fees left over after covering fees for this group FeeCredit uint64 } diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 76fa4912fc..350ab122d7 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -147,6 +147,10 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) tracer.cursorEvalTracer.AfterTxnGroup(ep, deltas, evalError) + if ep.GetCaller() == nil && deltas != nil { + // If this is the outer txn group, save the state delta + tracer.result.TxnGroups[0].Delta = *deltas + } } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { From d7fb785e341d1553c8a5ccd27169dd456b40b52c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 21 Apr 2023 12:09:36 -0700 Subject: [PATCH 10/26] Test AfterTxnGroup deltas --- .../logic/mocktracer/scenarios.go | 169 +++++++++++------- ledger/eval/eval_test.go | 56 ++++-- ledger/ledgercore/statedelta.go | 18 +- 3 files changed, 149 insertions(+), 94 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index b6edcdf935..fd4973eddd 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -88,7 +88,7 @@ type TestScenarioInfo struct { CreatedAppID basics.AppIndex } -func expectedApplyData(info TestScenarioInfo) transactions.ApplyData { +func expectedApplyDataAndStateDelta(info TestScenarioInfo, appCallProgram string, innerProgramBytes []byte) (transactions.ApplyData, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta) { expectedInnerAppCall := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: info.CreatedAppID.Address(), @@ -133,7 +133,8 @@ pushint 1`, Fee: info.MinFee, } expectedInnerPay2AD := transactions.ApplyData{} - return transactions.ApplyData{ + + expectedAD := transactions.ApplyData{ ApplicationID: info.CreatedAppID, EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, @@ -155,6 +156,80 @@ pushint 1`, Logs: []string{"a", "b", "c"}, }, } + + ops, err := logic.AssembleString(appCallProgram) + if err != nil { + panic(err) + } + + expectedSenderData := ledgercore.ToAccountData(info.SenderData) + expectedSenderData.MicroAlgos.Raw -= info.CallingTxn.Fee.Raw + expectedSenderData.TotalAppParams += 1 + expectedFeeSinkData := ledgercore.ToAccountData(info.FeeSinkData) + expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw + + var expectedDeltaCallingTxn ledgercore.StateDelta + expectedDeltaCallingTxn.Accts.Upsert(info.CallingTxn.Sender, expectedSenderData) + expectedDeltaCallingTxn.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + expectedDeltaCallingTxn.Accts.UpsertAppResource(info.CallingTxn.Sender, info.CreatedAppID, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: ops.Program, + ClearStateProgram: info.CallingTxn.ClearStateProgram, + StateSchemas: basics.StateSchemas{ + LocalStateSchema: info.CallingTxn.LocalStateSchema, + GlobalStateSchema: info.CallingTxn.GlobalStateSchema, + }, + ExtraProgramPages: info.CallingTxn.ExtraProgramPages, + }, + }, ledgercore.AppLocalStateDelta{}) + expectedDeltaCallingTxn.AddCreatable(basics.CreatableIndex(info.CreatedAppID), ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CallingTxn.Sender, + }) + + expectedAppAccountData := ledgercore.ToAccountData(info.AppAccountData) + expectedAppAccountData.TotalAppParams += 1 + expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw + + var expectedDeltaInnerAppCall ledgercore.StateDelta + expectedDeltaInnerAppCall.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) + expectedDeltaInnerAppCall.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + expectedDeltaInnerAppCall.Accts.UpsertAppResource(info.CreatedAppID.Address(), info.CreatedAppID+1, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: innerProgramBytes, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, // #pragma version 6; int 1; + }, + }, ledgercore.AppLocalStateDelta{}) + expectedDeltaInnerAppCall.AddCreatable(basics.CreatableIndex(info.CreatedAppID+1), ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CreatedAppID.Address(), + }) + + expectedAppAccountData.MicroAlgos.Raw -= 2 * info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += 2 * info.MinFee.Raw + + var expectedDeltaInnerPays ledgercore.StateDelta + expectedDeltaInnerPays.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) + expectedDeltaInnerPays.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + + return expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays +} + +func mergeDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { + var result ledgercore.StateDelta + for _, delta := range deltas { + result.Accts.MergeAccounts(delta.Accts) + for key, delta := range delta.KvMods { + result.AddKvMod(key, delta) + } + for id, delta := range delta.Creatables { + result.AddCreatable(id, delta) + } + } + return result } // TestScenarioOutcome represents an outcome of a TestScenario @@ -205,53 +280,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { noFailureName := "none" noFailure := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) program := fillProgramTemplate("", successInnerProgram, "", 1, 2, "pushint 1") - ops, err := logic.AssembleString(program) - if err != nil { - panic(err) - } - - expectedSenderData := ledgercore.ToAccountData(info.SenderData) - expectedSenderData.MicroAlgos.Raw -= info.CallingTxn.Fee.Raw - expectedSenderData.TotalAppParams += 1 - expectedAppAccountData := ledgercore.ToAccountData(info.AppAccountData) - expectedAppAccountData.MicroAlgos.Raw -= 3 * info.MinFee.Raw - expectedAppAccountData.TotalAppParams += 1 - expectedFeeSinkData := ledgercore.ToAccountData(info.FeeSinkData) - expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw + 3*info.MinFee.Raw - - var expectedDelta ledgercore.StateDelta - expectedDelta.Accts.Upsert(info.CallingTxn.Sender, expectedSenderData) - expectedDelta.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) - expectedDelta.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) - expectedDelta.Accts.UpsertAppResource(info.CallingTxn.Sender, info.CreatedAppID, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: ops.Program, - ClearStateProgram: info.CallingTxn.ClearStateProgram, - StateSchemas: basics.StateSchemas{ - LocalStateSchema: info.CallingTxn.LocalStateSchema, - GlobalStateSchema: info.CallingTxn.GlobalStateSchema, - }, - ExtraProgramPages: info.CallingTxn.ExtraProgramPages, - }, - }, ledgercore.AppLocalStateDelta{}) - expectedDelta.Accts.UpsertAppResource(info.CreatedAppID.Address(), info.CreatedAppID+1, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: successInnerProgramBytes, - ClearStateProgram: []byte{0x06, 0x81, 0x01}, // #pragma version 6; int 1; - }, - }, ledgercore.AppLocalStateDelta{}) - expectedDelta.AddCreatable(basics.CreatableIndex(info.CreatedAppID), ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: info.CallingTxn.Sender, - }) - expectedDelta.AddCreatable(basics.CreatableIndex(info.CreatedAppID+1), ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: info.CreatedAppID.Address(), - }) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := mergeDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays) return TestScenario{ Outcome: ApprovalOutcome, Program: program, @@ -325,8 +356,10 @@ func GetTestScenarios() map[string]TestScenarioGenerator { beforeInnersName := fmt.Sprintf("before inners,error=%t", shouldError) beforeInners := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) program := fillProgramTemplate(failureOps, successInnerProgram, "", 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + // EvalDeltas are removed from failed app call transactions expectedADNoED := expectedAD expectedADNoED.EvalDelta = transactions.EvalDelta{} @@ -350,6 +383,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 700, AppBudgetConsumed: 4, TxnAppBudgetConsumed: []uint64{0, 4}, @@ -359,7 +393,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { firstInnerName := fmt.Sprintf("first inner,error=%t", shouldError) firstInner := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", failureInnerProgram, "", 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, failureInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn // EvalDeltas are removed from failed app call transactions expectedInnerAppCallADNoEvalDelta := expectedAD.EvalDelta.InnerTxns[0].ApplyData @@ -372,8 +408,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.InnerTxns = expectedAD.EvalDelta.InnerTxns[:1] expectedAD.EvalDelta.InnerTxns[0].Txn.ApprovalProgram = failureInnerProgramBytes - - program := fillProgramTemplate("", failureInnerProgram, "", 1, 2, "pushint 1") return TestScenario{ Outcome: outcome, Program: program, @@ -402,6 +436,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 1400, AppBudgetConsumed: 15, TxnAppBudgetConsumed: []uint64{0, 15}, @@ -411,7 +446,10 @@ func GetTestScenarios() map[string]TestScenarioGenerator { betweenInnersName := fmt.Sprintf("between inners,error=%t", shouldError) betweenInners := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, failureOps, 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData // EvalDeltas are removed from failed app call transactions @@ -422,8 +460,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.Logs = expectedAD.EvalDelta.Logs[:2] expectedAD.EvalDelta.InnerTxns = expectedAD.EvalDelta.InnerTxns[:1] - - program := fillProgramTemplate("", successInnerProgram, failureOps, 1, 2, "pushint 1") return TestScenario{ Outcome: outcome, Program: program, @@ -455,6 +491,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 1400, AppBudgetConsumed: 19, TxnAppBudgetConsumed: []uint64{0, 19}, @@ -465,7 +502,10 @@ func GetTestScenarios() map[string]TestScenarioGenerator { if shouldError { secondInnerName := "second inner" secondInner := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, "", math.MaxUint64, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -477,8 +517,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.Logs = expectedAD.EvalDelta.Logs[:2] expectedAD.EvalDelta.InnerTxns[1].Txn.Amount.Raw = math.MaxUint64 - - program := fillProgramTemplate("", successInnerProgram, "", math.MaxUint64, 2, "pushint 1") return TestScenario{ Outcome: ErrorOutcome, Program: program, @@ -516,6 +554,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 32, TxnAppBudgetConsumed: []uint64{0, 32}, @@ -525,7 +564,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { thirdInnerName := "third inner" thirdInner := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, "", 1, math.MaxUint64, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -539,8 +580,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.Logs = expectedAD.EvalDelta.Logs[:2] expectedAD.EvalDelta.InnerTxns[2].Txn.Amount.Raw = math.MaxUint64 - - program := fillProgramTemplate("", successInnerProgram, "", 1, math.MaxUint64, "pushint 1") return TestScenario{ Outcome: ErrorOutcome, Program: program, @@ -580,6 +619,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 32, TxnAppBudgetConsumed: []uint64{0, 32}, @@ -590,14 +630,16 @@ func GetTestScenarios() map[string]TestScenarioGenerator { afterInnersName := fmt.Sprintf("after inners,error=%t", shouldError) afterInners := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, "", 1, 2, singleFailureOp) + expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData expectedInnerPay2AD := expectedAD.EvalDelta.InnerTxns[2].ApplyData // EvalDeltas are removed from failed app call transactions expectedADNoED := expectedAD expectedADNoED.EvalDelta = transactions.EvalDelta{} - program := fillProgramTemplate("", successInnerProgram, "", 1, 2, singleFailureOp) return TestScenario{ Outcome: outcome, Program: program, @@ -640,6 +682,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 35, TxnAppBudgetConsumed: []uint64{0, 35}, diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 704bd2bfe2..9b26cea56b 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -244,9 +244,14 @@ func TestTransactionGroupWithTracer(t *testing.T) { scenarios := mocktracer.GetTestScenarios() // TODO: remove this filter - scenarios = map[string]mocktracer.TestScenarioGenerator{ - "none": scenarios["none"], - } + // scenarios = map[string]mocktracer.TestScenarioGenerator{ + // "none": scenarios["none"], + // "before inners,error=true": scenarios["before inners,error=true"], + // "before inners,error=false": scenarios["before inners,error=false"], + // "first inner,error=true": scenarios["first inner,error=true"], + // "first inner,error=false": scenarios["first inner,error=false"], + // "between inners,error=true": scenarios["between inners,error=true"], + // } type tracerTestCase struct { name string @@ -255,7 +260,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { } var testCases []tracerTestCase - firstIteration := false // TODO: change back to true + firstIteration := true for scenarioName, scenario := range scenarios { firstTxnBehaviors := []string{"approve"} if firstIteration { @@ -358,11 +363,10 @@ int 1`, GenesisHash: genHash, } - // TODO: account for fee sink data changes from prior txns expectedFeeSinkData := balances[testSinkAddr] - expectedFeeSinkData.MicroAlgos.Raw += basicAppCallTxn.Amount + expectedFeeSinkData.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw if testCase.firstTxnBehavior == "approve" { - expectedFeeSinkData.MicroAlgos.Raw += payTxn.Amount + expectedFeeSinkData.MicroAlgos.Raw += payTxn.Txn().Fee.Raw } scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ @@ -423,7 +427,8 @@ int 1`, expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw expectedAcct0Data.TotalAppParams = 1 - expectedDelta := ledgercore.MakeStateDelta(&newBlock.BlockHeader, blkHeader.TimeStamp, 0, 0) + expectedBlockHeader := eval.block.BlockHeader + expectedDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) expectedDelta.Accts.Upsert(addrs[0], expectedAcct0Data) expectedDelta.Accts.Upsert(testSinkAddr, ledgercore.ToAccountData(expectedFeeSinkData)) expectedDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ @@ -437,19 +442,21 @@ int 1`, Created: true, Creator: addrs[0], }) - for i, stxn := range txgroup { - expectedDelta.Txids[stxn.ID()] = ledgercore.IncludedTransactions{ - LastValid: stxn.Txn.LastValid, - Intra: uint64(i), - } - } - // expectedDelta = eval.state.deltas() - var expectedEvents []mocktracer.Event + expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} if testCase.firstTxnBehavior == "approve" { err = eval.endOfBlock() require.NoError(t, err) + for i, stxn := range txgroup { + if i < 2 || scenario.Outcome == mocktracer.ApprovalOutcome { + expectedDelta.Txids[stxn.ID()] = ledgercore.IncludedTransactions{ + LastValid: stxn.Txn.LastValid, + Intra: uint64(i), + } + } + } + expectedAcct1Data := ledgercore.AccountData{} expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount @@ -482,8 +489,6 @@ int 1`, scenario.ExpectedEvents, { mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), - }, - { mocktracer.AfterBlock(eval.block.Round()), }, })...) @@ -506,8 +511,21 @@ int 1`, })...) } e := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) + + // TODO: remove extra checks require.Equal(t, len(expectedEvents), len(e)) - require.Equal(t, expectedEvents[len(expectedEvents)-1].Deltas, e[len(e)-1].Deltas) + var deltaIndex int + if testCase.firstTxnBehavior == "approve" { + // account for AfterBlock + deltaIndex = len(expectedEvents) - 2 + } else { + deltaIndex = len(expectedEvents) - 1 + } + jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[deltaIndex].Deltas) + jsonActualDelta := protocol.EncodeJSONStrict(e[deltaIndex].Deltas) + require.Equal(t, expectedEvents[deltaIndex].Deltas, e[deltaIndex].Deltas, "expected: %s\n\nactual %s", jsonExpectedDelta, jsonActualDelta) + // end extra checks + require.Equal(t, expectedEvents, e) }) } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 1a0f824de1..01f914d205 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -360,21 +360,15 @@ func (ad AccountDeltas) ModifiedAccounts() []basics.Address { // MergeAccounts applies other accounts into this StateDelta accounts func (ad *AccountDeltas) MergeAccounts(other AccountDeltas) { - for new := range other.Accts { - addr := other.Accts[new].Addr - acct := other.Accts[new].AccountData - ad.Upsert(addr, acct) + for _, balanceRecord := range other.Accts { + ad.Upsert(balanceRecord.Addr, balanceRecord.AccountData) } - for aapp, idx := range other.appResourcesCache { - params := other.AppResources[idx].Params - state := other.AppResources[idx].State - ad.UpsertAppResource(aapp.Address, aapp.App, params, state) + for _, appResource := range other.AppResources { + ad.UpsertAppResource(appResource.Addr, appResource.Aidx, appResource.Params, appResource.State) } - for aapp, idx := range other.assetResourcesCache { - params := other.AssetResources[idx].Params - holding := other.AssetResources[idx].Holding - ad.UpsertAssetResource(aapp.Address, aapp.Asset, params, holding) + for _, assetResource := range other.AssetResources { + ad.UpsertAssetResource(assetResource.Addr, assetResource.Aidx, assetResource.Params, assetResource.Holding) } } From a492003749e5e9555ba4a3299b5c0f9d737ade62 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 21 Apr 2023 12:09:52 -0700 Subject: [PATCH 11/26] Start to test AfterTxn deltas (granular eval) --- .../logic/mocktracer/scenarios.go | 104 +++- ledger/eval/eval_test.go | 508 ++++++++++-------- ledger/ledgercore/statedelta.go | 1 - 3 files changed, 352 insertions(+), 261 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index fd4973eddd..38a0167e9f 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/crypto" "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/txntest" @@ -86,6 +87,9 @@ type TestScenarioInfo struct { FeeSinkAddr basics.Address MinFee basics.MicroAlgos CreatedAppID basics.AppIndex + GranularEval bool + BlockHeader bookkeeping.BlockHeader + PrevTimestamp int64 } func expectedApplyDataAndStateDelta(info TestScenarioInfo, appCallProgram string, innerProgramBytes []byte) (transactions.ApplyData, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta) { @@ -168,7 +172,7 @@ pushint 1`, expectedFeeSinkData := ledgercore.ToAccountData(info.FeeSinkData) expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw - var expectedDeltaCallingTxn ledgercore.StateDelta + expectedDeltaCallingTxn := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) expectedDeltaCallingTxn.Accts.Upsert(info.CallingTxn.Sender, expectedSenderData) expectedDeltaCallingTxn.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) expectedDeltaCallingTxn.Accts.UpsertAppResource(info.CallingTxn.Sender, info.CreatedAppID, ledgercore.AppParamsDelta{ @@ -187,13 +191,19 @@ pushint 1`, Created: true, Creator: info.CallingTxn.Sender, }) + // Cannot call info.CallingTxn.ID() yet, since the txn and its group are not yet final. Instead, + // use the Txid zero value as a placeholder. It's up to the caller to update this if they need it. + expectedDeltaCallingTxn.Txids[transactions.Txid{}] = ledgercore.IncludedTransactions{ + LastValid: info.CallingTxn.LastValid, + Intra: 0, + } expectedAppAccountData := ledgercore.ToAccountData(info.AppAccountData) expectedAppAccountData.TotalAppParams += 1 expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw - var expectedDeltaInnerAppCall ledgercore.StateDelta + expectedDeltaInnerAppCall := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) expectedDeltaInnerAppCall.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) expectedDeltaInnerAppCall.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) expectedDeltaInnerAppCall.Accts.UpsertAppResource(info.CreatedAppID.Address(), info.CreatedAppID+1, ledgercore.AppParamsDelta{ @@ -211,27 +221,13 @@ pushint 1`, expectedAppAccountData.MicroAlgos.Raw -= 2 * info.MinFee.Raw expectedFeeSinkData.MicroAlgos.Raw += 2 * info.MinFee.Raw - var expectedDeltaInnerPays ledgercore.StateDelta + expectedDeltaInnerPays := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) expectedDeltaInnerPays.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) expectedDeltaInnerPays.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) return expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays } -func mergeDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { - var result ledgercore.StateDelta - for _, delta := range deltas { - result.Accts.MergeAccounts(delta.Accts) - for key, delta := range delta.KvMods { - result.AddKvMod(key, delta) - } - for id, delta := range delta.Creatables { - result.AddCreatable(id, delta) - } - } - return result -} - // TestScenarioOutcome represents an outcome of a TestScenario type TestScenarioOutcome int @@ -282,7 +278,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { noFailure := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", 1, 2, "pushint 1") expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) - expectedDelta := mergeDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays) + expectedDelta := MergeStateDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays) + + // remove all creatables from granular delta + for txid := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, txid) + } + return TestScenario{ Outcome: ApprovalOutcome, Program: program, @@ -303,7 +305,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, nil, false), + AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -312,16 +314,16 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, nil, false), + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPays, info.GranularEval), false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, nil, false), + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPays, info.GranularEval), false), // TODO: this is wrong because both txns are palaced in the same delta AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedAD, nil, false), + AfterTxn(protocol.ApplicationCallTx, expectedAD, StateDeltaIfTrue(expectedDelta, info.GranularEval), false), }, }), ExpectedSimulationAD: expectedAD, @@ -709,3 +711,61 @@ func StripInnerTxnGroupIDsFromEvents(events []Event) []Event { } return events } + +func HydrateStateDeltas(events []Event) []Event { + for i := range events { + deltas := events[i].Deltas + if deltas == nil { + continue + } + if deltas.Accts.AppResources == nil { + deltas.Accts.AppResources = make([]ledgercore.AppResourceRecord, 0) + } + if deltas.Creatables == nil { + deltas.Creatables = make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable) + } + } + return events +} + +func MergeStateDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { + if len(deltas) == 0 { + return ledgercore.StateDelta{} + } + + result := ledgercore.StateDelta{ + // copy basic fields from the first delta + Hdr: deltas[0].Hdr, + StateProofNext: deltas[0].StateProofNext, + PrevTimestamp: deltas[0].PrevTimestamp, + Totals: deltas[0].Totals, + + // initialize structure for later use + Txids: make(map[transactions.Txid]ledgercore.IncludedTransactions), + } + for _, delta := range deltas { + result.Accts.MergeAccounts(delta.Accts) + for key, delta := range delta.KvMods { + result.AddKvMod(key, delta) + } + for id, delta := range delta.Creatables { + result.AddCreatable(id, delta) + } + txidBase := uint64(len(result.Txids)) + for txid, includedTx := range delta.Txids { + includedTx.Intra += txidBase + result.Txids[txid] = includedTx + } + for lease, round := range delta.Txleases { + result.Txleases[lease] = round + } + } + return result +} + +func StateDeltaIfTrue(delta ledgercore.StateDelta, cond bool) *ledgercore.StateDelta { + if cond { + return &delta + } + return nil +} diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 9b26cea56b..07433018b6 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -244,14 +244,14 @@ func TestTransactionGroupWithTracer(t *testing.T) { scenarios := mocktracer.GetTestScenarios() // TODO: remove this filter - // scenarios = map[string]mocktracer.TestScenarioGenerator{ - // "none": scenarios["none"], - // "before inners,error=true": scenarios["before inners,error=true"], - // "before inners,error=false": scenarios["before inners,error=false"], - // "first inner,error=true": scenarios["first inner,error=true"], - // "first inner,error=false": scenarios["first inner,error=false"], - // "between inners,error=true": scenarios["between inners,error=true"], - // } + scenarios = map[string]mocktracer.TestScenarioGenerator{ + "none": scenarios["none"], + // "before inners,error=true": scenarios["before inners,error=true"], + // "before inners,error=false": scenarios["before inners,error=false"], + // "first inner,error=true": scenarios["first inner,error=true"], + // "first inner,error=false": scenarios["first inner,error=false"], + // "between inners,error=true": scenarios["between inners,error=true"], + } type tracerTestCase struct { name string @@ -283,250 +283,282 @@ func TestTransactionGroupWithTracer(t *testing.T) { testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - genesisInitState, addrs, keys := ledgertesting.Genesis(10) - - innerAppID := basics.AppIndex(3) - innerAppAddress := innerAppID.Address() - balances := genesisInitState.Accounts - balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) - - genesisBalances := bookkeeping.GenesisBalances{ - Balances: balances, - FeeSink: testSinkAddr, - RewardsPool: testPoolAddr, - Timestamp: 0, - } - l := newTestLedger(t, genesisBalances) + for _, granularEval := range []bool{false, true} { + granularEval := granularEval + t.Run(fmt.Sprintf("granularEval=%t", granularEval), func(t *testing.T) { + t.Parallel() + genesisInitState, addrs, keys := ledgertesting.Genesis(10) + + innerAppID := basics.AppIndex(3) + innerAppAddress := innerAppID.Address() + balances := genesisInitState.Accounts + balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) + + genesisBalances := bookkeeping.GenesisBalances{ + Balances: balances, + 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) - tracer := &mocktracer.Tracer{} - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, tracer) - require.NoError(t, err) - eval.validate = true - eval.generate = true - - genHash := l.GenesisHash() - - var basicAppCallReturn string - switch testCase.firstTxnBehavior { - case "approve": - basicAppCallReturn = "int 1" - case "reject": - basicAppCallReturn = "int 0" - case "error": - basicAppCallReturn = "err" - default: - require.Fail(t, "Unexpected firstTxnBehavior") - } - // a basic app call - basicAppCallTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: addrs[0], - ApprovalProgram: fmt.Sprintf(`#pragma version 6 -byte "hello" -log -%s`, basicAppCallReturn), - ClearStateProgram: `#pragma version 6 -int 1`, - - FirstValid: newBlock.Round(), - LastValid: newBlock.Round() + 1000, - Fee: minFee, - GenesisHash: genHash, - } + blkHeader, err := l.BlockHdr(basics.Round(0)) + require.NoError(t, err) + newBlock := bookkeeping.MakeBlock(blkHeader) + tracer := &mocktracer.Tracer{} + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, tracer) + require.NoError(t, err) + eval.validate = true + eval.generate = true + eval.GranularEval = granularEval + + genHash := l.GenesisHash() + + var basicAppCallReturn string + switch testCase.firstTxnBehavior { + case "approve": + basicAppCallReturn = "int 1" + case "reject": + basicAppCallReturn = "int 0" + case "error": + basicAppCallReturn = "err" + default: + require.Fail(t, "Unexpected firstTxnBehavior") + } + // a basic app call + basicAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: fmt.Sprintf(`#pragma version 6 + byte "hello" + log + %s`, basicAppCallReturn), + ClearStateProgram: `#pragma version 6 + int 1`, + + 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 with inner txn - innerAppCallTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: addrs[4], - ClearStateProgram: `#pragma version 6 -int 1`, - - 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 with inner txn + innerAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[4], + ClearStateProgram: `#pragma version 6 + int 1`, + + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + } - expectedFeeSinkData := balances[testSinkAddr] - expectedFeeSinkData.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw - if testCase.firstTxnBehavior == "approve" { - expectedFeeSinkData.MicroAlgos.Raw += payTxn.Txn().Fee.Raw - } + expectedFeeSinkDataForScenario := balances[testSinkAddr] + expectedFeeSinkDataForScenario.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw + if testCase.firstTxnBehavior == "approve" { + expectedFeeSinkDataForScenario.MicroAlgos.Raw += payTxn.Txn().Fee.Raw + } - scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ - CallingTxn: innerAppCallTxn.Txn(), - SenderData: balances[addrs[4]], - AppAccountData: balances[innerAppAddress], - FeeSinkData: expectedFeeSinkData, - FeeSinkAddr: testSinkAddr, - MinFee: minFee, - CreatedAppID: innerAppID, - }) - innerAppCallTxn.ApprovalProgram = scenario.Program + scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ + CallingTxn: innerAppCallTxn.Txn(), + SenderData: balances[addrs[4]], + AppAccountData: balances[innerAppAddress], + FeeSinkData: expectedFeeSinkDataForScenario, + FeeSinkAddr: testSinkAddr, + MinFee: minFee, + CreatedAppID: innerAppID, + GranularEval: granularEval, + BlockHeader: eval.block.BlockHeader, + PrevTimestamp: blkHeader.TimeStamp, + }) + innerAppCallTxn.ApprovalProgram = scenario.Program + + txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + + // Update the expected state delta to reflect the inner app call txid + scenarioTxidValue, ok := scenario.ExpectedStateDelta.Txids[transactions.Txid{}] + if ok { + delete(scenario.ExpectedStateDelta.Txids, transactions.Txid{}) + scenario.ExpectedStateDelta.Txids[innerAppCallTxn.Txn().ID()] = scenarioTxidValue + } + for i := range scenario.ExpectedEvents { + deltas := scenario.ExpectedEvents[i].Deltas + if deltas == nil { + continue + } + txidValue, ok := deltas.Txids[transactions.Txid{}] + if ok { + delete(deltas.Txids, transactions.Txid{}) + deltas.Txids[innerAppCallTxn.Txn().ID()] = txidValue + } + } - txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ + basicAppCallTxn.Txn().Sign(keys[0]), + payTxn.Txn().Sign(keys[1]), + innerAppCallTxn.Txn().Sign(keys[4]), + }) - txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ - basicAppCallTxn.Txn().Sign(keys[0]), - payTxn.Txn().Sign(keys[1]), - innerAppCallTxn.Txn().Sign(keys[4]), - }) + require.Len(t, eval.block.Payset, 0) - require.Len(t, eval.block.Payset, 0) + err = eval.TransactionGroup(txgroup) + switch testCase.firstTxnBehavior { + case "approve": + if len(scenario.ExpectedError) != 0 { + require.ErrorContains(t, err, scenario.ExpectedError) + require.Len(t, eval.block.Payset, 0) + } else { + require.NoError(t, err) + require.Len(t, eval.block.Payset, 3) + } + case "reject": + require.ErrorContains(t, err, "transaction rejected by ApprovalProgram") + require.Len(t, eval.block.Payset, 0) + case "error": + require.ErrorContains(t, err, "logic eval error: err opcode executed") + require.Len(t, eval.block.Payset, 0) + } - err = eval.TransactionGroup(txgroup) - switch testCase.firstTxnBehavior { - case "approve": - if len(scenario.ExpectedError) != 0 { - require.ErrorContains(t, err, scenario.ExpectedError) - require.Len(t, eval.block.Payset, 0) - } else { - require.NoError(t, err) - require.Len(t, eval.block.Payset, 3) - } - case "reject": - require.ErrorContains(t, err, "transaction rejected by ApprovalProgram") - require.Len(t, eval.block.Payset, 0) - case "error": - require.ErrorContains(t, err, "logic eval error: err opcode executed") - require.Len(t, eval.block.Payset, 0) - } + expectedBasicAppCallAD := transactions.ApplyData{ + ApplicationID: 1, + EvalDelta: transactions.EvalDelta{ + GlobalDelta: basics.StateDelta{}, + LocalDeltas: map[uint64]basics.StateDelta{}, + Logs: []string{"hello"}, + }, + } + expectedPayTxnAD := + transactions.ApplyData{ + ClosingAmount: basics.MicroAlgos{ + Raw: balances[payTxn.Sender].MicroAlgos.Raw - payTxn.Amount - txgroup[1].Txn.Fee.Raw, + }, + } - expectedBasicAppCallAD := transactions.ApplyData{ - ApplicationID: 1, - EvalDelta: transactions.EvalDelta{ - GlobalDelta: basics.StateDelta{}, - LocalDeltas: map[uint64]basics.StateDelta{}, - Logs: []string{"hello"}, - }, - } - expectedPayTxnAD := - transactions.ApplyData{ - ClosingAmount: basics.MicroAlgos{ - Raw: balances[payTxn.Sender].MicroAlgos.Raw - payTxn.Amount - txgroup[1].Txn.Fee.Raw, - }, - } - - expectedAcct0Data := ledgercore.ToAccountData(balances[addrs[0]]) - expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw - expectedAcct0Data.TotalAppParams = 1 - - expectedBlockHeader := eval.block.BlockHeader - expectedDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) - expectedDelta.Accts.Upsert(addrs[0], expectedAcct0Data) - expectedDelta.Accts.Upsert(testSinkAddr, ledgercore.ToAccountData(expectedFeeSinkData)) - expectedDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: txgroup[0].Txn.ApprovalProgram, - ClearStateProgram: txgroup[0].Txn.ClearStateProgram, - }, - }, ledgercore.AppLocalStateDelta{}) - expectedDelta.AddCreatable(1, ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: addrs[0], - }) + expectedFeeSinkData := ledgercore.ToAccountData(balances[testSinkAddr]) + expectedFeeSinkData.MicroAlgos.Raw += txgroup[0].Txn.Fee.Raw + expectedAcct0Data := ledgercore.ToAccountData(balances[addrs[0]]) + expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw + expectedAcct0Data.TotalAppParams = 1 + + expectedBlockHeader := eval.block.BlockHeader + expectedBasicAppCallDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) + expectedBasicAppCallDelta.Accts.Upsert(addrs[0], expectedAcct0Data) + expectedBasicAppCallDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) + expectedBasicAppCallDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: txgroup[0].Txn.ApprovalProgram, + ClearStateProgram: txgroup[0].Txn.ClearStateProgram, + }, + }, ledgercore.AppLocalStateDelta{}) + expectedBasicAppCallDelta.AddCreatable(1, ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: addrs[0], + }) + expectedBasicAppCallDelta.Txids[txgroup[0].Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: txgroup[0].Txn.LastValid, + Intra: 0, + } - expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} - if testCase.firstTxnBehavior == "approve" { - err = eval.endOfBlock() - require.NoError(t, err) + expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} + if testCase.firstTxnBehavior == "approve" { + err = eval.endOfBlock() + require.NoError(t, err) + + expectedAcct1Data := ledgercore.AccountData{} + expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) + expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount + expectedAcct3Data := ledgercore.ToAccountData(balances[addrs[3]]) + expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw + expectedFeeSinkData.MicroAlgos.Raw += txgroup[1].Txn.Fee.Raw + + expectedPayTxnDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) + expectedPayTxnDelta.Accts.Upsert(addrs[1], expectedAcct1Data) + expectedPayTxnDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) + expectedPayTxnDelta.Accts.Upsert(addrs[2], expectedAcct2Data) + expectedPayTxnDelta.Accts.Upsert(addrs[3], expectedAcct3Data) + expectedPayTxnDelta.Txids[txgroup[1].Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: txgroup[1].Txn.LastValid, + Intra: 0, // will be incremented once merged + } + + expectedDelta := mocktracer.MergeStateDeltas(expectedBasicAppCallDelta, expectedPayTxnDelta, scenario.ExpectedStateDelta) - for i, stxn := range txgroup { - if i < 2 || scenario.Outcome == mocktracer.ApprovalOutcome { - expectedDelta.Txids[stxn.ID()] = ledgercore.IncludedTransactions{ - LastValid: stxn.Txn.LastValid, - Intra: uint64(i), + // If the scenario failed, we expect the failed txn ID to be removed from the group state delta + if scenario.Outcome != mocktracer.ApprovalOutcome { + delete(expectedDelta.Txids, txgroup[2].ID()) + } + + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ + { + mocktracer.BeforeTxnGroup(3), + mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn + mocktracer.BeforeProgram(logic.ModeApp), + }, + mocktracer.OpcodeEvents(3, false), + { + mocktracer.AfterProgram(logic.ModeApp, false), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, granularEval), false), // end basicAppCallTxn + mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn + mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, mocktracer.StateDeltaIfTrue(expectedPayTxnDelta, granularEval), false), // end payTxn + }, + scenario.ExpectedEvents, + { + mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), + mocktracer.AfterBlock(eval.block.Round()), + }, + })...) + } else { + // Removed failed txid from expected state delta + delete(expectedBasicAppCallDelta.Txids, txgroup[0].Txn.ID()) + + hasError := testCase.firstTxnBehavior == "error" + // EvalDeltas are removed from failed app call transactions + expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ + { + mocktracer.BeforeTxnGroup(3), + mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn + mocktracer.BeforeProgram(logic.ModeApp), + }, + mocktracer.OpcodeEvents(3, hasError), + { + mocktracer.AfterProgram(logic.ModeApp, hasError), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, granularEval), true), // end basicAppCallTxn + mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true), + }, + })...) + } + mocktracer.HydrateStateDeltas(expectedEvents) + actualEvents := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) + + // These extra tests are not necessary for correctness, but they provide more targeted information on failure + if assert.Equal(t, len(expectedEvents), len(actualEvents)) { + for i := range expectedEvents { + jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) + jsonActualDelta := protocol.EncodeJSONStrict(actualEvents[i].Deltas) + assert.Equal(t, expectedEvents[i].Deltas, actualEvents[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expectedEvents[i].Type, actualEvents[i].Type, jsonExpectedDelta, jsonActualDelta) } } - } - - expectedAcct1Data := ledgercore.AccountData{} - expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) - expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount - expectedAcct3Data := ledgercore.ToAccountData(balances[addrs[3]]) - expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw - expectedDelta.Accts.Upsert(addrs[1], expectedAcct1Data) - expectedDelta.Accts.Upsert(addrs[2], expectedAcct2Data) - expectedDelta.Accts.Upsert(addrs[3], expectedAcct3Data) - expectedDelta.Accts.MergeAccounts(scenario.ExpectedStateDelta.Accts) - for key, delta := range scenario.ExpectedStateDelta.KvMods { - expectedDelta.AddKvMod(key, delta) - } - for id, delta := range scenario.ExpectedStateDelta.Creatables { - expectedDelta.AddCreatable(id, delta) - } - - expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ - { - mocktracer.BeforeTxnGroup(3), - mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn - mocktracer.BeforeProgram(logic.ModeApp), - }, - mocktracer.OpcodeEvents(3, false), - { - mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, nil, false), // end basicAppCallTxn - mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn - mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, nil, false), // end payTxn - }, - scenario.ExpectedEvents, - { - mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), - mocktracer.AfterBlock(eval.block.Round()), - }, - })...) - } else { - hasError := testCase.firstTxnBehavior == "error" - // EvalDeltas are removed from failed app call transactions - expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} - expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ - { - mocktracer.BeforeTxnGroup(3), - mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn - mocktracer.BeforeProgram(logic.ModeApp), - }, - mocktracer.OpcodeEvents(3, hasError), - { - mocktracer.AfterProgram(logic.ModeApp, hasError), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, nil, true), // end basicAppCallTxn - mocktracer.AfterTxnGroup(3, &expectedDelta, true), - }, - })...) - } - e := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) - - // TODO: remove extra checks - require.Equal(t, len(expectedEvents), len(e)) - var deltaIndex int - if testCase.firstTxnBehavior == "approve" { - // account for AfterBlock - deltaIndex = len(expectedEvents) - 2 - } else { - deltaIndex = len(expectedEvents) - 1 - } - jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[deltaIndex].Deltas) - jsonActualDelta := protocol.EncodeJSONStrict(e[deltaIndex].Deltas) - require.Equal(t, expectedEvents[deltaIndex].Deltas, e[deltaIndex].Deltas, "expected: %s\n\nactual %s", jsonExpectedDelta, jsonActualDelta) - // end extra checks - require.Equal(t, expectedEvents, e) + require.Equal(t, expectedEvents, actualEvents) + }) + } }) } } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 01f914d205..429ac300f4 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -363,7 +363,6 @@ func (ad *AccountDeltas) MergeAccounts(other AccountDeltas) { for _, balanceRecord := range other.Accts { ad.Upsert(balanceRecord.Addr, balanceRecord.AccountData) } - for _, appResource := range other.AppResources { ad.UpsertAppResource(appResource.Addr, appResource.Aidx, appResource.Params, appResource.State) } From ed8ff749b7c3baf25b8a9fe8273d8359c42bb233 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 21 Apr 2023 16:41:55 -0700 Subject: [PATCH 12/26] TestTransactionGroupWithTracer working --- .../logic/mocktracer/scenarios.go | 139 +++++++++++------- ledger/eval/eval_test.go | 34 +++-- ledger/ledgercore/statedelta.go | 63 ++++++++ 3 files changed, 176 insertions(+), 60 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 38a0167e9f..0466282b05 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -92,7 +92,7 @@ type TestScenarioInfo struct { PrevTimestamp int64 } -func expectedApplyDataAndStateDelta(info TestScenarioInfo, appCallProgram string, innerProgramBytes []byte) (transactions.ApplyData, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta) { +func expectedApplyDataAndStateDelta(info TestScenarioInfo, appCallProgram string, innerProgramBytes []byte) (transactions.ApplyData, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta) { expectedInnerAppCall := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: info.CreatedAppID.Address(), @@ -218,14 +218,21 @@ pushint 1`, Creator: info.CreatedAppID.Address(), }) - expectedAppAccountData.MicroAlgos.Raw -= 2 * info.MinFee.Raw - expectedFeeSinkData.MicroAlgos.Raw += 2 * info.MinFee.Raw + expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw + + expectedDeltaInnerPay1 := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) + expectedDeltaInnerPay1.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) + expectedDeltaInnerPay1.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + + expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw - expectedDeltaInnerPays := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) - expectedDeltaInnerPays.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) - expectedDeltaInnerPays.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + expectedDeltaInnerPay2 := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) + expectedDeltaInnerPay2.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) + expectedDeltaInnerPay2.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) - return expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays + return expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 } // TestScenarioOutcome represents an outcome of a TestScenario @@ -277,12 +284,12 @@ func GetTestScenarios() map[string]TestScenarioGenerator { noFailureName := "none" noFailure := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", 1, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) - expectedDelta := MergeStateDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPays) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := MergeStateDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2) // remove all creatables from granular delta - for txid := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, txid) + for i := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, i) } return TestScenario{ @@ -314,9 +321,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPays, info.GranularEval), false), + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPays, info.GranularEval), false), // TODO: this is wrong because both txns are palaced in the same delta + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPay2, info.GranularEval), false), AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, @@ -359,9 +366,12 @@ func GetTestScenarios() map[string]TestScenarioGenerator { beforeInnersName := fmt.Sprintf("before inners,error=%t", shouldError) beforeInners := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate(failureOps, successInnerProgram, "", 1, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn + // remove failed txids from granular delta + expectedDeltaCallingTxn.Txids = nil + // EvalDeltas are removed from failed app call transactions expectedADNoED := expectedAD expectedADNoED.EvalDelta = transactions.EvalDelta{} @@ -381,7 +391,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(4, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), }, }), ExpectedSimulationAD: expectedAD, @@ -396,9 +406,18 @@ func GetTestScenarios() map[string]TestScenarioGenerator { firstInnerName := fmt.Sprintf("first inner,error=%t", shouldError) firstInner := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", failureInnerProgram, "", 1, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, failureInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, _, _ := expectedApplyDataAndStateDelta(info, program, failureInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn + // remove failed txids from granular delta + expectedDeltaCallingTxn.Txids = nil + expectedDeltaInnerAppCall.Txids = nil + + // remove all creatables from granular delta + for i := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, i) + } + // EvalDeltas are removed from failed app call transactions expectedInnerAppCallADNoEvalDelta := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerAppCallADNoEvalDelta.EvalDelta = transactions.EvalDelta{} @@ -430,11 +449,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), true), AfterTxnGroup(1, nil, true), // end first itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), }, }), ExpectedSimulationAD: expectedAD, @@ -449,9 +468,17 @@ func GetTestScenarios() map[string]TestScenarioGenerator { betweenInnersName := fmt.Sprintf("between inners,error=%t", shouldError) betweenInners := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, failureOps, 1, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn + // remove failed txids from granular delta + expectedDeltaCallingTxn.Txids = nil + + // remove all creatables from granular delta + for i := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, i) + } + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData // EvalDeltas are removed from failed app call transactions @@ -482,14 +509,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(4, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), }, }), ExpectedSimulationAD: expectedAD, @@ -505,9 +532,18 @@ func GetTestScenarios() map[string]TestScenarioGenerator { secondInnerName := "second inner" secondInner := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", math.MaxUint64, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn + // remove failed txids from granular delta + expectedDeltaCallingTxn.Txids = nil + expectedDeltaInnerPay1.Txids = nil + + // remove all creatables from granular delta + for i := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, i) + } + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -539,7 +575,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -548,11 +584,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, nil, true), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), true), AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), }, }), ExpectedSimulationAD: expectedAD, @@ -567,9 +603,18 @@ func GetTestScenarios() map[string]TestScenarioGenerator { thirdInnerName := "third inner" thirdInner := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", 1, math.MaxUint64, "pushint 1") - expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn + // remove failed txids from granular delta + expectedDeltaCallingTxn.Txids = nil + expectedDeltaInnerPay2.Txids = nil + + // remove all creatables from granular delta + for i := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, i) + } + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData expectedInnerPay2AD := expectedAD.EvalDelta.InnerTxns[2].ApplyData @@ -602,7 +647,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -611,13 +656,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, nil, false), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, nil, true), + AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, StateDeltaIfTrue(expectedDeltaInnerPay2, info.GranularEval), true), AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), }, }), ExpectedSimulationAD: expectedAD, @@ -633,9 +678,17 @@ func GetTestScenarios() map[string]TestScenarioGenerator { afterInnersName := fmt.Sprintf("after inners,error=%t", shouldError) afterInners := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", 1, 2, singleFailureOp) - expectedAD, expectedDeltaCallingTxn, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn + // remove failed txids from granular delta + expectedDeltaCallingTxn.Txids = nil + + // remove all creatables from granular delta + for i := range expectedDeltaInnerAppCall.Creatables { + delete(expectedDeltaInnerAppCall.Creatables, i) + } + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData expectedInnerPay2AD := expectedAD.EvalDelta.InnerTxns[2].ApplyData @@ -662,7 +715,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, nil, false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -671,16 +724,16 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, nil, false), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, nil, false), + AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, StateDeltaIfTrue(expectedDeltaInnerPay2, info.GranularEval), false), AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, nil, true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), }, }), ExpectedSimulationAD: expectedAD, @@ -712,22 +765,6 @@ func StripInnerTxnGroupIDsFromEvents(events []Event) []Event { return events } -func HydrateStateDeltas(events []Event) []Event { - for i := range events { - deltas := events[i].Deltas - if deltas == nil { - continue - } - if deltas.Accts.AppResources == nil { - deltas.Accts.AppResources = make([]ledgercore.AppResourceRecord, 0) - } - if deltas.Creatables == nil { - deltas.Creatables = make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable) - } - } - return events -} - func MergeStateDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { if len(deltas) == 0 { return ledgercore.StateDelta{} diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 07433018b6..e3038078df 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -244,14 +244,19 @@ func TestTransactionGroupWithTracer(t *testing.T) { scenarios := mocktracer.GetTestScenarios() // TODO: remove this filter - scenarios = map[string]mocktracer.TestScenarioGenerator{ - "none": scenarios["none"], - // "before inners,error=true": scenarios["before inners,error=true"], - // "before inners,error=false": scenarios["before inners,error=false"], - // "first inner,error=true": scenarios["first inner,error=true"], - // "first inner,error=false": scenarios["first inner,error=false"], - // "between inners,error=true": scenarios["between inners,error=true"], - } + // scenarios = map[string]mocktracer.TestScenarioGenerator{ + // "none": scenarios["none"], + // "before inners,error=true": scenarios["before inners,error=true"], + // "before inners,error=false": scenarios["before inners,error=false"], + // "first inner,error=true": scenarios["first inner,error=true"], + // "first inner,error=false": scenarios["first inner,error=false"], + // "between inners,error=true": scenarios["between inners,error=true"], + // "between inners,error=false": scenarios["between inners,error=false"], + // "second inner": scenarios["second inner"], + // "third inner": scenarios["third inner"], + // "after inners,error=true": scenarios["after inners,error=true"], + // "after inners,error=false": scenarios["after inners,error=false"], + // } type tracerTestCase struct { name string @@ -544,9 +549,20 @@ func TestTransactionGroupWithTracer(t *testing.T) { }, })...) } - mocktracer.HydrateStateDeltas(expectedEvents) actualEvents := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) + // Dehydrate deltas for easier comparison + for i := range actualEvents { + if actualEvents[i].Deltas != nil { + actualEvents[i].Deltas.Dehydrate() + } + } + for i := range expectedEvents { + if expectedEvents[i].Deltas != nil { + expectedEvents[i].Deltas.Dehydrate() + } + } + // These extra tests are not necessary for correctness, but they provide more targeted information on failure if assert.Equal(t, len(expectedEvents), len(actualEvents)) { for i := range expectedEvents { diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 429ac300f4..dd654a43dd 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -219,6 +219,27 @@ func (sd *StateDelta) PopulateStateDelta(hdr *bookkeeping.BlockHeader, prevTimes sd.PrevTimestamp = prevTimestamp } +func (sd *StateDelta) Hydrate() { + sd.Accts.Hydrate() +} + +func (sd *StateDelta) Dehydrate() { + sd.Accts.Dehydrate() + sd.initialHint = 0 + if sd.KvMods == nil { + sd.KvMods = make(map[string]KvValueDelta) + } + if sd.Txids == nil { + sd.Txids = make(map[transactions.Txid]IncludedTransactions) + } + if sd.Txleases == nil { + sd.Txleases = make(map[Txlease]basics.Round) + } + if sd.Creatables == nil { + sd.Creatables = make(map[basics.CreatableIndex]ModifiedCreatable) + } +} + // MakeAccountDeltas creates account delta // if adding new fields make sure to add them to the .reset() and .isEmpty() methods func MakeAccountDeltas(hint int) AccountDeltas { @@ -228,6 +249,48 @@ func MakeAccountDeltas(hint int) AccountDeltas { } } +func (ad *AccountDeltas) Hydrate() { + for idx, acct := range ad.Accts { + ad.acctsCache[acct.Addr] = idx + } + for idx, app := range ad.AppResources { + ad.appResourcesCache[AccountApp{app.Addr, app.Aidx}] = idx + } + for idx, asset := range ad.AssetResources { + ad.assetResourcesCache[AccountAsset{asset.Addr, asset.Aidx}] = idx + } +} + +func (ad *AccountDeltas) Dehydrate() { + if ad.Accts == nil { + ad.Accts = []BalanceRecord{} + } + if ad.AppResources == nil { + ad.AppResources = []AppResourceRecord{} + } + if ad.AssetResources == nil { + ad.AssetResources = []AssetResourceRecord{} + } + if ad.acctsCache == nil { + ad.acctsCache = make(map[basics.Address]int) + } + for key := range ad.acctsCache { + delete(ad.acctsCache, key) + } + if ad.appResourcesCache == nil { + ad.appResourcesCache = make(map[AccountApp]int) + } + for key := range ad.appResourcesCache { + delete(ad.appResourcesCache, key) + } + if ad.assetResourcesCache == nil { + ad.assetResourcesCache = make(map[AccountAsset]int) + } + for key := range ad.assetResourcesCache { + delete(ad.assetResourcesCache, key) + } +} + // Reset resets the StateDelta for re-use with sync.Pool func (sd *StateDelta) Reset() { sd.Accts.reset() From 32f43b5f78306d7151d5b573e21612c6434f7378 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 24 Apr 2023 14:08:44 -0700 Subject: [PATCH 13/26] Testing from simulation package --- .../logic/mocktracer/scenarios.go | 19 ++-- data/transactions/logic/tracer_test.go | 3 + ledger/eval/eval_test.go | 8 +- ledger/simulation/simulation_eval_test.go | 64 ++++++------- ledger/simulation/simulator_test.go | 95 ++++++++++++++++--- ledger/simulation/testing/utils.go | 59 ++++++++---- 6 files changed, 170 insertions(+), 78 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 0466282b05..72df59be76 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -81,9 +81,9 @@ func fillProgramTemplate(beforeInnersOps, innerApprovalProgram, betweenInnersOps // TestScenarioInfo holds arguments used to call a TestScenarioGenerator type TestScenarioInfo struct { CallingTxn transactions.Transaction - SenderData basics.AccountData - AppAccountData basics.AccountData - FeeSinkData basics.AccountData + SenderData ledgercore.AccountData + AppAccountData ledgercore.AccountData + FeeSinkData ledgercore.AccountData FeeSinkAddr basics.Address MinFee basics.MicroAlgos CreatedAppID basics.AppIndex @@ -166,10 +166,10 @@ pushint 1`, panic(err) } - expectedSenderData := ledgercore.ToAccountData(info.SenderData) + expectedSenderData := info.SenderData expectedSenderData.MicroAlgos.Raw -= info.CallingTxn.Fee.Raw - expectedSenderData.TotalAppParams += 1 - expectedFeeSinkData := ledgercore.ToAccountData(info.FeeSinkData) + expectedSenderData.TotalAppParams++ + expectedFeeSinkData := info.FeeSinkData expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw expectedDeltaCallingTxn := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) @@ -198,8 +198,8 @@ pushint 1`, Intra: 0, } - expectedAppAccountData := ledgercore.ToAccountData(info.AppAccountData) - expectedAppAccountData.TotalAppParams += 1 + expectedAppAccountData := info.AppAccountData + expectedAppAccountData.TotalAppParams++ expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw @@ -765,6 +765,8 @@ func StripInnerTxnGroupIDsFromEvents(events []Event) []Event { return events } +// MergeStateDeltas merges multiple state deltas into one. The arguments are not modified, but the +// first delta is used to populate non-mergeable fields in the result. func MergeStateDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { if len(deltas) == 0 { return ledgercore.StateDelta{} @@ -800,6 +802,7 @@ func MergeStateDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { return result } +// StateDeltaIfTrue returns a pointer to the given delta if the given condition is true, or nil func StateDeltaIfTrue(delta ledgercore.StateDelta, cond bool) *ledgercore.StateDelta { if cond { return &delta diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go index 9e5f9564b3..1b85358df3 100644 --- a/data/transactions/logic/tracer_test.go +++ b/data/transactions/logic/tracer_test.go @@ -143,6 +143,9 @@ func TestInnerAppEvalWithTracer(t *testing.T) { mock := mocktracer.Tracer{} ep, tx, ledger := MakeSampleEnv() ep.Tracer = &mock + // Note: it does not make sense to test ep.GranularEval=true here, + // since these tests use a testing Ledger, which does not use cows + // or StateDeltas internally. // Establish FirstTestID as the app id, and fund it. We do this so that the created // inner app will get a sequential ID, which is what the mocktracer scenarios expect diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index e3038078df..19350b332d 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -373,7 +373,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { GenesisHash: genHash, } - expectedFeeSinkDataForScenario := balances[testSinkAddr] + expectedFeeSinkDataForScenario := ledgercore.ToAccountData(balances[testSinkAddr]) expectedFeeSinkDataForScenario.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw if testCase.firstTxnBehavior == "approve" { expectedFeeSinkDataForScenario.MicroAlgos.Raw += payTxn.Txn().Fee.Raw @@ -381,8 +381,8 @@ func TestTransactionGroupWithTracer(t *testing.T) { scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ CallingTxn: innerAppCallTxn.Txn(), - SenderData: balances[addrs[4]], - AppAccountData: balances[innerAppAddress], + SenderData: ledgercore.ToAccountData(balances[addrs[4]]), + AppAccountData: ledgercore.ToAccountData(balances[innerAppAddress]), FeeSinkData: expectedFeeSinkDataForScenario, FeeSinkAddr: testSinkAddr, MinFee: minFee, @@ -563,7 +563,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { } } - // These extra tests are not necessary for correctness, but they provide more targeted information on failure + // These extra checks are not necessary for correctness, but they provide more targeted information on failure if assert.Equal(t, len(expectedEvents), len(actualEvents)) { for i := range expectedEvents { jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 1d95364126..30a955a65b 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -146,11 +146,11 @@ func validateSimulationResult(t *testing.T, result simulation.Result) { func simulationTest(t *testing.T, f func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase) { t.Helper() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := simulation.MakeSimulator(env.Ledger) - testcase := f(accounts, txnInfo) + testcase := f(env.Accounts, env.TxnInfo) actual, err := s.Simulate(testcase.input) require.NoError(t, err) @@ -471,12 +471,12 @@ func TestStateProofTxn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, _, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := simulation.MakeSimulator(env.Ledger) txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ + env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.StateProofTx, // No need to fill out StateProofTxnFields, this should fail at signature verification }).SignedTxn(), @@ -490,23 +490,23 @@ func TestSimpleGroupTxn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - sender1 := accounts[0].Addr - sender1Balance := accounts[0].AcctData.MicroAlgos - sender2 := accounts[1].Addr - sender2Balance := accounts[1].AcctData.MicroAlgos + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := simulation.MakeSimulator(env.Ledger) + sender1 := env.Accounts[0].Addr + sender1Balance := env.Accounts[0].AcctData.MicroAlgos + sender2 := env.Accounts[1].Addr + sender2Balance := env.Accounts[1].AcctData.MicroAlgos // Send money back and forth txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ + env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender1, Receiver: sender2, Amount: 1_000_000, }).SignedTxn(), - txnInfo.NewTxn(txntest.Txn{ + env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender2, Receiver: sender1, @@ -526,11 +526,11 @@ func TestSimpleGroupTxn(t *testing.T) { attachGroupID(txgroup) // Check balances before transaction - sender1Data, _, err := l.LookupWithoutRewards(l.Latest(), sender1) + sender1Data, _, err := env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender1) require.NoError(t, err) require.Equal(t, sender1Balance, sender1Data.MicroAlgos) - sender2Data, _, err := l.LookupWithoutRewards(l.Latest(), sender2) + sender2Data, _, err := env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender2) require.NoError(t, err) require.Equal(t, sender2Balance, sender2Data.MicroAlgos) @@ -543,11 +543,11 @@ func TestSimpleGroupTxn(t *testing.T) { require.Zero(t, result.TxnGroups[0].FailureMessage) // Confirm balances have not changed - sender1Data, _, err = l.LookupWithoutRewards(l.Latest(), sender1) + sender1Data, _, err = env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender1) require.NoError(t, err) require.Equal(t, sender1Balance, sender1Data.MicroAlgos) - sender2Data, _, err = l.LookupWithoutRewards(l.Latest(), sender2) + sender2Data, _, err = env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender2) require.NoError(t, err) require.Equal(t, sender2Balance, sender2Data.MicroAlgos) } @@ -1131,13 +1131,13 @@ func TestSignatureCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - sender := accounts[0].Addr + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := simulation.MakeSimulator(env.Ledger) + sender := env.Accounts[0].Addr txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ + env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender, Receiver: sender, @@ -1155,7 +1155,7 @@ func TestSignatureCheck(t *testing.T) { require.Zero(t, result.TxnGroups[0].FailureMessage) // add signature - signatureSecrets := accounts[0].Sk + signatureSecrets := env.Accounts[0].Sk txgroup[0] = txgroup[0].Txn.Sign(signatureSecrets) // should not error now that we have a signature @@ -1180,13 +1180,13 @@ func TestInvalidTxGroup(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - receiver := accounts[0].Addr + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := simulation.MakeSimulator(env.Ledger) + receiver := env.Accounts[0].Addr txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ + env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: ledgertesting.PoolAddr(), Receiver: receiver, diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 9b4bedc821..eef2f2e76e 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -22,14 +22,17 @@ import ( "github.com/algorand/go-algorand/crypto" "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/logic/mocktracer" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/eval" + "github.com/algorand/go-algorand/ledger/ledgercore" simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -40,7 +43,7 @@ func TestNonOverridenDataLedgerMethodsUseRoundParameter(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, _, _ := simulationtesting.PrepareSimulatorTest(t) + env := simulationtesting.PrepareSimulatorTest(t) // methods overriden by `simulatorLedger`` overridenMethods := []string{ @@ -89,7 +92,7 @@ func TestNonOverridenDataLedgerMethodsUseRoundParameter(t *testing.T) { return false } - ledgerType := reflect.TypeOf(l) + ledgerType := reflect.TypeOf(env.Ledger) for i := 0; i < ledgerType.NumMethod(); i++ { method := ledgerType.Method(i) if methodExistsInEvalLedger(method.Name) && !methodIsSkipped(method.Name) { @@ -104,10 +107,10 @@ func TestSimulateWithTrace(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := MakeSimulator(l) - sender := accounts[0] + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := MakeSimulator(env.Ledger) + sender := env.Accounts[0] op, err := logic.AssembleString(`#pragma version 8 int 1`) @@ -115,13 +118,13 @@ int 1`) program := logic.Program(op.Program) lsigAddr := basics.Address(crypto.HashObj(&program)) - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: lsigAddr, Amount: 1_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: lsigAddr, ApprovalProgram: `#pragma version 8 @@ -142,8 +145,49 @@ int 1`, block, _, err := s.simulateWithTracer(txgroup, mockTracer) require.NoError(t, err) - payset := block.Block().Payset - require.Len(t, payset, 2) + evalBlock := block.Block() + require.Len(t, evalBlock.Payset, 2) + + expectedSenderData := ledgercore.ToAccountData(sender.AcctData) + expectedSenderData.MicroAlgos.Raw -= signedPayTxn.Txn.Amount.Raw + signedPayTxn.Txn.Fee.Raw + expectedLsigData := ledgercore.AccountData{} + expectedLsigData.MicroAlgos.Raw += signedPayTxn.Txn.Amount.Raw - signedAppCallTxn.Txn.Fee.Raw + expectedLsigData.TotalAppParams = 1 + expectedFeeSinkData := ledgercore.ToAccountData(env.FeeSinkAccount.AcctData) + expectedFeeSinkData.MicroAlgos.Raw += signedPayTxn.Txn.Fee.Raw + signedAppCallTxn.Txn.Fee.Raw + + expectedAppID := evalBlock.Payset[1].ApplyData.ApplicationID + expectedAppParams := ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: signedAppCallTxn.Txn.ApprovalProgram, + ClearStateProgram: signedAppCallTxn.Txn.ClearStateProgram, + }, + } + + // Cannot use evalBlock directly because the tracer is called before many block details are finalized + expectedBlockHeader := bookkeeping.MakeBlock(env.TxnInfo.LatestHeader).BlockHeader + expectedBlockHeader.TimeStamp = evalBlock.TimeStamp + expectedBlockHeader.RewardsRate = evalBlock.RewardsRate + expectedBlockHeader.RewardsResidue = evalBlock.RewardsResidue + + expectedDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, env.TxnInfo.LatestHeader.TimeStamp, 0, 0) + expectedDelta.Accts.Upsert(sender.Addr, expectedSenderData) + expectedDelta.Accts.Upsert(env.FeeSinkAccount.Addr, expectedFeeSinkData) + expectedDelta.Accts.Upsert(lsigAddr, expectedLsigData) + expectedDelta.Accts.UpsertAppResource(lsigAddr, expectedAppID, expectedAppParams, ledgercore.AppLocalStateDelta{}) + expectedDelta.AddCreatable(basics.CreatableIndex(expectedAppID), ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: lsigAddr, + }) + expectedDelta.Txids[signedPayTxn.Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: signedPayTxn.Txn.LastValid, + Intra: 0, + } + expectedDelta.Txids[signedAppCallTxn.Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: signedAppCallTxn.Txn.LastValid, + Intra: 1, + } expectedEvents := []mocktracer.Event{ // LogicSig evaluation @@ -155,16 +199,39 @@ int 1`, mocktracer.BeforeBlock(block.Block().Round()), mocktracer.BeforeTxnGroup(2), mocktracer.BeforeTxn(protocol.PaymentTx), - mocktracer.AfterTxn(protocol.PaymentTx, payset[0].ApplyData, nil, false), + mocktracer.AfterTxn(protocol.PaymentTx, evalBlock.Payset[0].ApplyData, nil, false), mocktracer.BeforeTxn(protocol.ApplicationCallTx), mocktracer.BeforeProgram(logic.ModeApp), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, payset[1].ApplyData, nil, false), - mocktracer.AfterTxnGroup(2, nil, false), + mocktracer.AfterTxn(protocol.ApplicationCallTx, evalBlock.Payset[1].ApplyData, nil, false), + mocktracer.AfterTxnGroup(2, &expectedDelta, false), //Block evaluation mocktracer.AfterBlock(block.Block().Round()), } - require.Equal(t, expectedEvents, mockTracer.Events) + actualEvents := mockTracer.Events + + // Dehydrate deltas for better comparison + for i := range expectedEvents { + if expectedEvents[i].Deltas != nil { + expectedEvents[i].Deltas.Dehydrate() + } + } + for i := range actualEvents { + if actualEvents[i].Deltas != nil { + actualEvents[i].Deltas.Dehydrate() + } + } + + // These extra checks are not necessary for correctness, but they provide more targeted information on failure + if assert.Equal(t, len(expectedEvents), len(actualEvents)) { + for i := range expectedEvents { + jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) + jsonActualDelta := protocol.EncodeJSONStrict(actualEvents[i].Deltas) + assert.Equal(t, expectedEvents[i].Deltas, actualEvents[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expectedEvents[i].Type, actualEvents[i].Type, jsonExpectedDelta, jsonActualDelta) + } + } + + require.Equal(t, expectedEvents, actualEvents) } diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index a18a791e1c..e15811008c 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -76,8 +76,18 @@ func (info TxnInfo) InnerTxn(parent transactions.SignedTxn, inner txntest.Txn) t return inner } +// Environment contains the ledger and testing environment for transaction simulations +type Environment struct { + Ledger *data.Ledger + // Accounts is a list of all accounts in the ledger, excluding the fee sink and rewards pool + Accounts []Account + FeeSinkAccount Account + RewardsPoolAccount Account + TxnInfo TxnInfo +} + // PrepareSimulatorTest creates an environment to test transaction simulations -func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txnInfo TxnInfo) { +func PrepareSimulatorTest(t *testing.T) Environment { genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) // Prepare ledger @@ -89,28 +99,33 @@ func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txn realLedger, err := ledger.OpenLedger(log, t.Name(), inMem, genesisInitState, cfg) require.NoError(t, err, "could not open ledger") - l = &data.Ledger{Ledger: realLedger} - require.NotNil(t, l) + ledger := &data.Ledger{Ledger: realLedger} // Reformat accounts - accounts = make([]Account, len(keys)-2) // -2 for pool and sink accounts - i := 0 + accounts := make([]Account, 0, len(keys)-2) // -2 for pool and sink accounts + var feeSinkAccount Account + var rewardsPoolAccount Account for addr, key := range keys { - if addr == ledgertesting.PoolAddr() || addr == ledgertesting.SinkAddr() { - continue - } - - acctData := genesisInitState.Accounts[addr] - accounts[i] = Account{ + account := Account{ Addr: addr, Sk: key, - AcctData: acctData, + AcctData: genesisInitState.Accounts[addr], } - i++ + + if addr == ledgertesting.SinkAddr() { + feeSinkAccount = account + continue + } + if addr == ledgertesting.PoolAddr() { + rewardsPoolAccount = account + continue + } + + accounts = append(accounts, account) } - latest := l.Latest() - latestHeader, err := l.BlockHdr(latest) + latest := ledger.Latest() + latestHeader, err := ledger.BlockHdr(latest) require.NoError(t, err) rand.Seed(time.Now().UnixNano()) @@ -119,17 +134,21 @@ func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txn numBlocks := rand.Intn(4) for i := 0; i < numBlocks; i++ { nextBlock := bookkeeping.MakeBlock(latestHeader) - err = l.AddBlock(nextBlock, agreement.Certificate{}) + err = ledger.AddBlock(nextBlock, agreement.Certificate{}) require.NoError(t, err) // round has advanced by 1 - require.Equal(t, latest+1, l.Latest()) + require.Equal(t, latest+1, ledger.Latest()) latest++ latestHeader = nextBlock.BlockHeader } - txnInfo = TxnInfo{latestHeader} - - return + return Environment{ + Ledger: ledger, + Accounts: accounts, + FeeSinkAccount: feeSinkAccount, + RewardsPoolAccount: rewardsPoolAccount, + TxnInfo: TxnInfo{latestHeader}, + } } From f57040b20e224ff7a6b9bb2758ed7f90b308f60b Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 24 Apr 2023 14:38:32 -0700 Subject: [PATCH 14/26] Fix race and simplify test table --- .../logic/mocktracer/scenarios.go | 30 - ledger/eval/applications.go | 5 +- ledger/eval/eval_test.go | 580 +++++++++--------- 3 files changed, 293 insertions(+), 322 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 72df59be76..9c9c4e9f84 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -287,11 +287,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := MergeStateDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2) - // remove all creatables from granular delta - for i := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, i) - } - return TestScenario{ Outcome: ApprovalOutcome, Program: program, @@ -413,11 +408,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedDeltaCallingTxn.Txids = nil expectedDeltaInnerAppCall.Txids = nil - // remove all creatables from granular delta - for i := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, i) - } - // EvalDeltas are removed from failed app call transactions expectedInnerAppCallADNoEvalDelta := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerAppCallADNoEvalDelta.EvalDelta = transactions.EvalDelta{} @@ -474,11 +464,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { // remove failed txids from granular delta expectedDeltaCallingTxn.Txids = nil - // remove all creatables from granular delta - for i := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, i) - } - expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData // EvalDeltas are removed from failed app call transactions @@ -539,11 +524,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedDeltaCallingTxn.Txids = nil expectedDeltaInnerPay1.Txids = nil - // remove all creatables from granular delta - for i := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, i) - } - expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -610,11 +590,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedDeltaCallingTxn.Txids = nil expectedDeltaInnerPay2.Txids = nil - // remove all creatables from granular delta - for i := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, i) - } - expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData expectedInnerPay2AD := expectedAD.EvalDelta.InnerTxns[2].ApplyData @@ -684,11 +659,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { // remove failed txids from granular delta expectedDeltaCallingTxn.Txids = nil - // remove all creatables from granular delta - for i := range expectedDeltaInnerAppCall.Creatables { - delete(expectedDeltaInnerAppCall.Creatables, i) - } - expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData expectedInnerPay2AD := expectedAD.EvalDelta.InnerTxns[2].ApplyData diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 3b453078db..7d7ee30989 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -304,7 +304,10 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgerco d := cowForTxn.deltas() deltas = &d cowForTxn.commitToParent() - cowForTxn.recycle() + // cowForTxn.recycle() // DO NOT RECYCLE! + // We cannot recycle here since we are returning the underlying StateDelta for this cow. + // If we did recycle, there would be a race between the caller looking at the StateDelta + // and the next user of the recycled cow. }() } diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 19350b332d..bb174c0843 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -262,25 +262,29 @@ func TestTransactionGroupWithTracer(t *testing.T) { name string firstTxnBehavior string innerAppCallScenario mocktracer.TestScenarioGenerator + granularEval bool } var testCases []tracerTestCase - firstIteration := true - for scenarioName, scenario := range scenarios { - firstTxnBehaviors := []string{"approve"} - if firstIteration { - // When the first transaction rejects or errors, the behavior of the later transactions - // don't matter, so we only want to test these cases with any one mocktracer scenario. - firstTxnBehaviors = append(firstTxnBehaviors, "reject", "error") - firstIteration = false - } + for _, granularEval := range []bool{false, true} { + firstIteration := true + for scenarioName, scenario := range scenarios { + firstTxnBehaviors := []string{"approve"} + if firstIteration { + // When the first transaction rejects or errors, the behavior of the later transactions + // don't matter, so we only want to test these cases with any one mocktracer scenario. + firstTxnBehaviors = append(firstTxnBehaviors, "reject", "error") + firstIteration = false + } - for _, firstTxnTxnBehavior := range firstTxnBehaviors { - testCases = append(testCases, tracerTestCase{ - name: fmt.Sprintf("firstTxnBehavior=%s,scenario=%s", firstTxnTxnBehavior, scenarioName), - firstTxnBehavior: firstTxnTxnBehavior, - innerAppCallScenario: scenario, - }) + for _, firstTxnTxnBehavior := range firstTxnBehaviors { + testCases = append(testCases, tracerTestCase{ + name: fmt.Sprintf("firstTxnBehavior=%s,scenario=%s,granularEval=%t", firstTxnTxnBehavior, scenarioName, granularEval), + firstTxnBehavior: firstTxnTxnBehavior, + innerAppCallScenario: scenario, + granularEval: granularEval, + }) + } } } @@ -288,293 +292,287 @@ func TestTransactionGroupWithTracer(t *testing.T) { testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - for _, granularEval := range []bool{false, true} { - granularEval := granularEval - t.Run(fmt.Sprintf("granularEval=%t", granularEval), func(t *testing.T) { - t.Parallel() - genesisInitState, addrs, keys := ledgertesting.Genesis(10) - - innerAppID := basics.AppIndex(3) - innerAppAddress := innerAppID.Address() - balances := genesisInitState.Accounts - balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) - - genesisBalances := bookkeeping.GenesisBalances{ - Balances: balances, - 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) - tracer := &mocktracer.Tracer{} - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, tracer) - require.NoError(t, err) - eval.validate = true - eval.generate = true - eval.GranularEval = granularEval - - genHash := l.GenesisHash() - - var basicAppCallReturn string - switch testCase.firstTxnBehavior { - case "approve": - basicAppCallReturn = "int 1" - case "reject": - basicAppCallReturn = "int 0" - case "error": - basicAppCallReturn = "err" - default: - require.Fail(t, "Unexpected firstTxnBehavior") - } - // a basic app call - basicAppCallTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: addrs[0], - ApprovalProgram: fmt.Sprintf(`#pragma version 6 + genesisInitState, addrs, keys := ledgertesting.Genesis(10) + + innerAppID := basics.AppIndex(3) + innerAppAddress := innerAppID.Address() + balances := genesisInitState.Accounts + balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) + + genesisBalances := bookkeeping.GenesisBalances{ + Balances: balances, + 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) + tracer := &mocktracer.Tracer{} + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, tracer) + require.NoError(t, err) + eval.validate = true + eval.generate = true + eval.GranularEval = testCase.granularEval + + genHash := l.GenesisHash() + + var basicAppCallReturn string + switch testCase.firstTxnBehavior { + case "approve": + basicAppCallReturn = "int 1" + case "reject": + basicAppCallReturn = "int 0" + case "error": + basicAppCallReturn = "err" + default: + require.Fail(t, "Unexpected firstTxnBehavior") + } + // a basic app call + basicAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: fmt.Sprintf(`#pragma version 6 byte "hello" log %s`, basicAppCallReturn), - ClearStateProgram: `#pragma version 6 + ClearStateProgram: `#pragma version 6 int 1`, - 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 with inner txn - innerAppCallTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: addrs[4], - ClearStateProgram: `#pragma version 6 + 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 with inner txn + innerAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[4], + ClearStateProgram: `#pragma version 6 int 1`, - FirstValid: newBlock.Round(), - LastValid: newBlock.Round() + 1000, - Fee: minFee, - GenesisHash: genHash, - } - - expectedFeeSinkDataForScenario := ledgercore.ToAccountData(balances[testSinkAddr]) - expectedFeeSinkDataForScenario.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw - if testCase.firstTxnBehavior == "approve" { - expectedFeeSinkDataForScenario.MicroAlgos.Raw += payTxn.Txn().Fee.Raw - } - - scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ - CallingTxn: innerAppCallTxn.Txn(), - SenderData: ledgercore.ToAccountData(balances[addrs[4]]), - AppAccountData: ledgercore.ToAccountData(balances[innerAppAddress]), - FeeSinkData: expectedFeeSinkDataForScenario, - FeeSinkAddr: testSinkAddr, - MinFee: minFee, - CreatedAppID: innerAppID, - GranularEval: granularEval, - BlockHeader: eval.block.BlockHeader, - PrevTimestamp: blkHeader.TimeStamp, - }) - innerAppCallTxn.ApprovalProgram = scenario.Program - - txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) - - // Update the expected state delta to reflect the inner app call txid - scenarioTxidValue, ok := scenario.ExpectedStateDelta.Txids[transactions.Txid{}] - if ok { - delete(scenario.ExpectedStateDelta.Txids, transactions.Txid{}) - scenario.ExpectedStateDelta.Txids[innerAppCallTxn.Txn().ID()] = scenarioTxidValue - } - for i := range scenario.ExpectedEvents { - deltas := scenario.ExpectedEvents[i].Deltas - if deltas == nil { - continue - } - txidValue, ok := deltas.Txids[transactions.Txid{}] - if ok { - delete(deltas.Txids, transactions.Txid{}) - deltas.Txids[innerAppCallTxn.Txn().ID()] = txidValue - } - } - - txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ - basicAppCallTxn.Txn().Sign(keys[0]), - payTxn.Txn().Sign(keys[1]), - innerAppCallTxn.Txn().Sign(keys[4]), - }) + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + } + expectedFeeSinkDataForScenario := ledgercore.ToAccountData(balances[testSinkAddr]) + expectedFeeSinkDataForScenario.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw + if testCase.firstTxnBehavior == "approve" { + expectedFeeSinkDataForScenario.MicroAlgos.Raw += payTxn.Txn().Fee.Raw + } + + scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ + CallingTxn: innerAppCallTxn.Txn(), + SenderData: ledgercore.ToAccountData(balances[addrs[4]]), + AppAccountData: ledgercore.ToAccountData(balances[innerAppAddress]), + FeeSinkData: expectedFeeSinkDataForScenario, + FeeSinkAddr: testSinkAddr, + MinFee: minFee, + CreatedAppID: innerAppID, + GranularEval: testCase.granularEval, + BlockHeader: eval.block.BlockHeader, + PrevTimestamp: blkHeader.TimeStamp, + }) + innerAppCallTxn.ApprovalProgram = scenario.Program + + txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + + // Update the expected state delta to reflect the inner app call txid + scenarioTxidValue, ok := scenario.ExpectedStateDelta.Txids[transactions.Txid{}] + if ok { + delete(scenario.ExpectedStateDelta.Txids, transactions.Txid{}) + scenario.ExpectedStateDelta.Txids[innerAppCallTxn.Txn().ID()] = scenarioTxidValue + } + for i := range scenario.ExpectedEvents { + deltas := scenario.ExpectedEvents[i].Deltas + if deltas == nil { + continue + } + txidValue, ok := deltas.Txids[transactions.Txid{}] + if ok { + delete(deltas.Txids, transactions.Txid{}) + deltas.Txids[innerAppCallTxn.Txn().ID()] = txidValue + } + } + + txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ + basicAppCallTxn.Txn().Sign(keys[0]), + payTxn.Txn().Sign(keys[1]), + innerAppCallTxn.Txn().Sign(keys[4]), + }) + + require.Len(t, eval.block.Payset, 0) + + err = eval.TransactionGroup(txgroup) + switch testCase.firstTxnBehavior { + case "approve": + if len(scenario.ExpectedError) != 0 { + require.ErrorContains(t, err, scenario.ExpectedError) require.Len(t, eval.block.Payset, 0) + } else { + require.NoError(t, err) + require.Len(t, eval.block.Payset, 3) + } + case "reject": + require.ErrorContains(t, err, "transaction rejected by ApprovalProgram") + require.Len(t, eval.block.Payset, 0) + case "error": + require.ErrorContains(t, err, "logic eval error: err opcode executed") + require.Len(t, eval.block.Payset, 0) + } - err = eval.TransactionGroup(txgroup) - switch testCase.firstTxnBehavior { - case "approve": - if len(scenario.ExpectedError) != 0 { - require.ErrorContains(t, err, scenario.ExpectedError) - require.Len(t, eval.block.Payset, 0) - } else { - require.NoError(t, err) - require.Len(t, eval.block.Payset, 3) - } - case "reject": - require.ErrorContains(t, err, "transaction rejected by ApprovalProgram") - require.Len(t, eval.block.Payset, 0) - case "error": - require.ErrorContains(t, err, "logic eval error: err opcode executed") - require.Len(t, eval.block.Payset, 0) - } - - expectedBasicAppCallAD := transactions.ApplyData{ - ApplicationID: 1, - EvalDelta: transactions.EvalDelta{ - GlobalDelta: basics.StateDelta{}, - LocalDeltas: map[uint64]basics.StateDelta{}, - Logs: []string{"hello"}, - }, - } - expectedPayTxnAD := - transactions.ApplyData{ - ClosingAmount: basics.MicroAlgos{ - Raw: balances[payTxn.Sender].MicroAlgos.Raw - payTxn.Amount - txgroup[1].Txn.Fee.Raw, - }, - } - - expectedFeeSinkData := ledgercore.ToAccountData(balances[testSinkAddr]) - expectedFeeSinkData.MicroAlgos.Raw += txgroup[0].Txn.Fee.Raw - expectedAcct0Data := ledgercore.ToAccountData(balances[addrs[0]]) - expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw - expectedAcct0Data.TotalAppParams = 1 - - expectedBlockHeader := eval.block.BlockHeader - expectedBasicAppCallDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) - expectedBasicAppCallDelta.Accts.Upsert(addrs[0], expectedAcct0Data) - expectedBasicAppCallDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) - expectedBasicAppCallDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: txgroup[0].Txn.ApprovalProgram, - ClearStateProgram: txgroup[0].Txn.ClearStateProgram, - }, - }, ledgercore.AppLocalStateDelta{}) - expectedBasicAppCallDelta.AddCreatable(1, ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: addrs[0], - }) - expectedBasicAppCallDelta.Txids[txgroup[0].Txn.ID()] = ledgercore.IncludedTransactions{ - LastValid: txgroup[0].Txn.LastValid, - Intra: 0, - } - - expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} - if testCase.firstTxnBehavior == "approve" { - err = eval.endOfBlock() - require.NoError(t, err) - - expectedAcct1Data := ledgercore.AccountData{} - expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) - expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount - expectedAcct3Data := ledgercore.ToAccountData(balances[addrs[3]]) - expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw - expectedFeeSinkData.MicroAlgos.Raw += txgroup[1].Txn.Fee.Raw - - expectedPayTxnDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) - expectedPayTxnDelta.Accts.Upsert(addrs[1], expectedAcct1Data) - expectedPayTxnDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) - expectedPayTxnDelta.Accts.Upsert(addrs[2], expectedAcct2Data) - expectedPayTxnDelta.Accts.Upsert(addrs[3], expectedAcct3Data) - expectedPayTxnDelta.Txids[txgroup[1].Txn.ID()] = ledgercore.IncludedTransactions{ - LastValid: txgroup[1].Txn.LastValid, - Intra: 0, // will be incremented once merged - } - - expectedDelta := mocktracer.MergeStateDeltas(expectedBasicAppCallDelta, expectedPayTxnDelta, scenario.ExpectedStateDelta) - - // If the scenario failed, we expect the failed txn ID to be removed from the group state delta - if scenario.Outcome != mocktracer.ApprovalOutcome { - delete(expectedDelta.Txids, txgroup[2].ID()) - } - - expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ - { - mocktracer.BeforeTxnGroup(3), - mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn - mocktracer.BeforeProgram(logic.ModeApp), - }, - mocktracer.OpcodeEvents(3, false), - { - mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, granularEval), false), // end basicAppCallTxn - mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn - mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, mocktracer.StateDeltaIfTrue(expectedPayTxnDelta, granularEval), false), // end payTxn - }, - scenario.ExpectedEvents, - { - mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), - mocktracer.AfterBlock(eval.block.Round()), - }, - })...) - } else { - // Removed failed txid from expected state delta - delete(expectedBasicAppCallDelta.Txids, txgroup[0].Txn.ID()) - - hasError := testCase.firstTxnBehavior == "error" - // EvalDeltas are removed from failed app call transactions - expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} - expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ - { - mocktracer.BeforeTxnGroup(3), - mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn - mocktracer.BeforeProgram(logic.ModeApp), - }, - mocktracer.OpcodeEvents(3, hasError), - { - mocktracer.AfterProgram(logic.ModeApp, hasError), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, granularEval), true), // end basicAppCallTxn - mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true), - }, - })...) - } - actualEvents := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) - - // Dehydrate deltas for easier comparison - for i := range actualEvents { - if actualEvents[i].Deltas != nil { - actualEvents[i].Deltas.Dehydrate() - } - } - for i := range expectedEvents { - if expectedEvents[i].Deltas != nil { - expectedEvents[i].Deltas.Dehydrate() - } - } - - // These extra checks are not necessary for correctness, but they provide more targeted information on failure - if assert.Equal(t, len(expectedEvents), len(actualEvents)) { - for i := range expectedEvents { - jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) - jsonActualDelta := protocol.EncodeJSONStrict(actualEvents[i].Deltas) - assert.Equal(t, expectedEvents[i].Deltas, actualEvents[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expectedEvents[i].Type, actualEvents[i].Type, jsonExpectedDelta, jsonActualDelta) - } - } - - require.Equal(t, expectedEvents, actualEvents) - }) + expectedBasicAppCallAD := transactions.ApplyData{ + ApplicationID: 1, + EvalDelta: transactions.EvalDelta{ + GlobalDelta: basics.StateDelta{}, + LocalDeltas: map[uint64]basics.StateDelta{}, + Logs: []string{"hello"}, + }, } + expectedPayTxnAD := + transactions.ApplyData{ + ClosingAmount: basics.MicroAlgos{ + Raw: balances[payTxn.Sender].MicroAlgos.Raw - payTxn.Amount - txgroup[1].Txn.Fee.Raw, + }, + } + + expectedFeeSinkData := ledgercore.ToAccountData(balances[testSinkAddr]) + expectedFeeSinkData.MicroAlgos.Raw += txgroup[0].Txn.Fee.Raw + expectedAcct0Data := ledgercore.ToAccountData(balances[addrs[0]]) + expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw + expectedAcct0Data.TotalAppParams = 1 + + expectedBlockHeader := eval.block.BlockHeader + expectedBasicAppCallDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) + expectedBasicAppCallDelta.Accts.Upsert(addrs[0], expectedAcct0Data) + expectedBasicAppCallDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) + expectedBasicAppCallDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: txgroup[0].Txn.ApprovalProgram, + ClearStateProgram: txgroup[0].Txn.ClearStateProgram, + }, + }, ledgercore.AppLocalStateDelta{}) + expectedBasicAppCallDelta.AddCreatable(1, ledgercore.ModifiedCreatable{ + Ctype: basics.AppCreatable, + Created: true, + Creator: addrs[0], + }) + expectedBasicAppCallDelta.Txids[txgroup[0].Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: txgroup[0].Txn.LastValid, + Intra: 0, + } + + expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} + if testCase.firstTxnBehavior == "approve" { + err = eval.endOfBlock() + require.NoError(t, err) + + expectedAcct1Data := ledgercore.AccountData{} + expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) + expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount + expectedAcct3Data := ledgercore.ToAccountData(balances[addrs[3]]) + expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw + expectedFeeSinkData.MicroAlgos.Raw += txgroup[1].Txn.Fee.Raw + + expectedPayTxnDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) + expectedPayTxnDelta.Accts.Upsert(addrs[1], expectedAcct1Data) + expectedPayTxnDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) + expectedPayTxnDelta.Accts.Upsert(addrs[2], expectedAcct2Data) + expectedPayTxnDelta.Accts.Upsert(addrs[3], expectedAcct3Data) + expectedPayTxnDelta.Txids[txgroup[1].Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: txgroup[1].Txn.LastValid, + Intra: 0, // will be incremented once merged + } + + expectedDelta := mocktracer.MergeStateDeltas(expectedBasicAppCallDelta, expectedPayTxnDelta, scenario.ExpectedStateDelta) + + // If the scenario failed, we expect the failed txn ID to be removed from the group state delta + if scenario.Outcome != mocktracer.ApprovalOutcome { + delete(expectedDelta.Txids, txgroup[2].ID()) + } + + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ + { + mocktracer.BeforeTxnGroup(3), + mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn + mocktracer.BeforeProgram(logic.ModeApp), + }, + mocktracer.OpcodeEvents(3, false), + { + mocktracer.AfterProgram(logic.ModeApp, false), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, testCase.granularEval), false), // end basicAppCallTxn + mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn + mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, mocktracer.StateDeltaIfTrue(expectedPayTxnDelta, testCase.granularEval), false), // end payTxn + }, + scenario.ExpectedEvents, + { + mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), + mocktracer.AfterBlock(eval.block.Round()), + }, + })...) + } else { + // Removed failed txid from expected state delta + delete(expectedBasicAppCallDelta.Txids, txgroup[0].Txn.ID()) + + hasError := testCase.firstTxnBehavior == "error" + // EvalDeltas are removed from failed app call transactions + expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ + { + mocktracer.BeforeTxnGroup(3), + mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn + mocktracer.BeforeProgram(logic.ModeApp), + }, + mocktracer.OpcodeEvents(3, hasError), + { + mocktracer.AfterProgram(logic.ModeApp, hasError), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, testCase.granularEval), true), // end basicAppCallTxn + mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true), + }, + })...) + } + actualEvents := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) + + // Dehydrate deltas for easier comparison + for i := range actualEvents { + if actualEvents[i].Deltas != nil { + actualEvents[i].Deltas.Dehydrate() + } + } + for i := range expectedEvents { + if expectedEvents[i].Deltas != nil { + expectedEvents[i].Deltas.Dehydrate() + } + } + + // These extra checks are not necessary for correctness, but they provide more targeted information on failure + if assert.Equal(t, len(expectedEvents), len(actualEvents)) { + for i := range expectedEvents { + jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) + jsonActualDelta := protocol.EncodeJSONStrict(actualEvents[i].Deltas) + assert.Equal(t, expectedEvents[i].Deltas, actualEvents[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expectedEvents[i].Type, actualEvents[i].Type, jsonExpectedDelta, jsonActualDelta) + } + } + + require.Equal(t, expectedEvents, actualEvents) }) } } From 106aed562f74d68b3ee5bd29be7f59d7a3c6afda Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 24 Apr 2023 14:42:31 -0700 Subject: [PATCH 15/26] Document statedelta methods --- ledger/ledgercore/statedelta.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index dd654a43dd..9d07b73aea 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -219,10 +219,13 @@ func (sd *StateDelta) PopulateStateDelta(hdr *bookkeeping.BlockHeader, prevTimes sd.PrevTimestamp = prevTimestamp } +// Hydrate reverses the effects of Dehydrate, restoring internal data. func (sd *StateDelta) Hydrate() { sd.Accts.Hydrate() } +// Dehydrate normalized the fields of this StateDelta, and clears any redundant internal caching. +// This is useful for comparing StateDelta objects during testing. func (sd *StateDelta) Dehydrate() { sd.Accts.Dehydrate() sd.initialHint = 0 @@ -249,6 +252,7 @@ func MakeAccountDeltas(hint int) AccountDeltas { } } +// Hydrate reverses the effects of Dehydrate, restoring internal data. func (ad *AccountDeltas) Hydrate() { for idx, acct := range ad.Accts { ad.acctsCache[acct.Addr] = idx @@ -261,6 +265,8 @@ func (ad *AccountDeltas) Hydrate() { } } +// Dehydrate normalized the fields of this AccountDeltas, and clears any redundant internal caching. +// This is useful for comparing AccountDeltas objects during testing. func (ad *AccountDeltas) Dehydrate() { if ad.Accts == nil { ad.Accts = []BalanceRecord{} From 7ca0a1d03d5e9bb1ca5ac78b67ede5bae3cf2943 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 3 May 2023 10:13:06 -0700 Subject: [PATCH 16/26] Remove StateDelta from simulation.Result --- ledger/eval/eval_test.go | 8 +++--- ledger/simulation/simulation_eval_test.go | 35 ++++------------------- ledger/simulation/trace.go | 2 -- ledger/simulation/tracer.go | 4 --- 4 files changed, 9 insertions(+), 40 deletions(-) diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 465c589f4f..505892eaf9 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -335,11 +335,11 @@ func TestTransactionGroupWithTracer(t *testing.T) { Type: protocol.ApplicationCallTx, Sender: addrs[0], ApprovalProgram: fmt.Sprintf(`#pragma version 6 - byte "hello" - log +byte "hello" +log %s`, basicAppCallReturn), ClearStateProgram: `#pragma version 6 - int 1`, +int 1`, FirstValid: newBlock.Round(), LastValid: newBlock.Round() + 1000, @@ -365,7 +365,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { Type: protocol.ApplicationCallTx, Sender: addrs[4], ClearStateProgram: `#pragma version 6 - int 1`, +int 1`, FirstValid: newBlock.Round(), LastValid: newBlock.Round() + 1000, diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index dae30ed7d9..613d3a6e25 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -30,7 +30,6 @@ import ( "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/ledger/simulation" simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -173,12 +172,6 @@ func TestPayTxn(t *testing.T) { Amount: 1_000_000, }).Txn().Sign(sender.Sk) - expectedSenderAcctData := sender.AcctData - expectedSenderAcctData.MicroAlgos.Raw -= txn.Txn.Fee.Raw + txn.Txn.Amount.Raw - - expectedReceiverAcctData := receiver.AcctData - expectedReceiverAcctData.MicroAlgos.Raw += txn.Txn.Amount.Raw - return simulationTestCase{ input: simulation.Request{ TxnGroups: [][]transactions.SignedTxn{{txn}}, @@ -191,24 +184,6 @@ func TestPayTxn(t *testing.T) { Txns: []simulation.TxnResult{{}}, }, }, - Delta: ledgercore.StateDelta{ - Accts: ledgercore.AccountDeltas{ - Accts: []ledgercore.BalanceRecord{ - { - Addr: sender.Addr, - AccountData: ledgercore.ToAccountData(expectedSenderAcctData), - }, - { - Addr: ledgertesting.SinkAddr(), - // AccountData: ledgercore.ToAccountData(ledgercore.AccountData{}), - }, - { - Addr: receiver.Addr, - AccountData: ledgercore.ToAccountData(expectedReceiverAcctData), - }, - }, - }, - }, }, } }) @@ -1463,12 +1438,12 @@ func TestOptionalSignaturesIncorrect(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - sender := accounts[0] + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Ledger.Close() + s := simulation.MakeSimulator(env.Ledger) + sender := env.Accounts[0] - stxn := txnInfo.NewTxn(txntest.Txn{ + stxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index aecb3a6252..1c7ecda34b 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -40,8 +40,6 @@ type TxnResult struct { // TxnGroupResult contains the simulation result for a single transaction group type TxnGroupResult struct { Txns []TxnResult - // Delta is the state delta for this group. - Delta ledgercore.StateDelta // FailureMessage will be the error message for the first transaction in the group which errors. // If the group succeeds, this will be empty. FailureMessage string diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 5b83e62103..2c565f9bcf 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -147,10 +147,6 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) tracer.cursorEvalTracer.AfterTxnGroup(ep, deltas, evalError) - if ep.GetCaller() == nil && deltas != nil { - // If this is the outer txn group, save the state delta - tracer.result.TxnGroups[0].Delta = *deltas - } } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { From 1652a5cbe2c498bd3d7d99bb7d064290c93114c4 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 3 May 2023 14:36:02 -0700 Subject: [PATCH 17/26] Use StateDelta.Hydrate() --- data/transactions/logic/eval.go | 4 +- .../logic/mocktracer/scenarios.go | 156 +++++++++++++----- data/transactions/logic/mocktracer/tracer.go | 33 ++++ ledger/eval/eval_test.go | 135 ++++++++------- ledger/ledgercore/statedelta.go | 11 ++ ledger/simulation/simulator_test.go | 86 +++++----- 6 files changed, 277 insertions(+), 148 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 9f04772bef..b577ec1a96 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5435,10 +5435,10 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxn(ep, i) } - update, err := cx.Ledger.Perform(i, ep) + deltas, err := cx.Ledger.Perform(i, ep) if ep.Tracer != nil { - ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, update, err) + ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, deltas, err) } if err != nil { diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 9c9c4e9f84..0d10f2b21c 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -172,65 +172,139 @@ pushint 1`, expectedFeeSinkData := info.FeeSinkData expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw - expectedDeltaCallingTxn := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) - expectedDeltaCallingTxn.Accts.Upsert(info.CallingTxn.Sender, expectedSenderData) - expectedDeltaCallingTxn.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) - expectedDeltaCallingTxn.Accts.UpsertAppResource(info.CallingTxn.Sender, info.CreatedAppID, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: ops.Program, - ClearStateProgram: info.CallingTxn.ClearStateProgram, - StateSchemas: basics.StateSchemas{ - LocalStateSchema: info.CallingTxn.LocalStateSchema, - GlobalStateSchema: info.CallingTxn.GlobalStateSchema, + expectedDeltaCallingTxn := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CallingTxn.Sender, + AccountData: expectedSenderData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: info.CreatedAppID, + Addr: info.CallingTxn.Sender, + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: ops.Program, + ClearStateProgram: info.CallingTxn.ClearStateProgram, + StateSchemas: basics.StateSchemas{ + LocalStateSchema: info.CallingTxn.LocalStateSchema, + GlobalStateSchema: info.CallingTxn.GlobalStateSchema, + }, + ExtraProgramPages: info.CallingTxn.ExtraProgramPages, + }, + }, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(info.CreatedAppID): { + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CallingTxn.Sender, + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + // Cannot call info.CallingTxn.ID() yet, since the txn and its group are not yet final. Instead, + // use the Txid zero value as a placeholder. It's up to the caller to update this if they need it. + {}: { + LastValid: info.CallingTxn.LastValid, + Intra: 0, }, - ExtraProgramPages: info.CallingTxn.ExtraProgramPages, }, - }, ledgercore.AppLocalStateDelta{}) - expectedDeltaCallingTxn.AddCreatable(basics.CreatableIndex(info.CreatedAppID), ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: info.CallingTxn.Sender, - }) - // Cannot call info.CallingTxn.ID() yet, since the txn and its group are not yet final. Instead, - // use the Txid zero value as a placeholder. It's up to the caller to update this if they need it. - expectedDeltaCallingTxn.Txids[transactions.Txid{}] = ledgercore.IncludedTransactions{ - LastValid: info.CallingTxn.LastValid, - Intra: 0, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, } + expectedDeltaCallingTxn.Hydrate() expectedAppAccountData := info.AppAccountData expectedAppAccountData.TotalAppParams++ expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw - expectedDeltaInnerAppCall := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) - expectedDeltaInnerAppCall.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) - expectedDeltaInnerAppCall.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) - expectedDeltaInnerAppCall.Accts.UpsertAppResource(info.CreatedAppID.Address(), info.CreatedAppID+1, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: innerProgramBytes, - ClearStateProgram: []byte{0x06, 0x81, 0x01}, // #pragma version 6; int 1; + expectedDeltaInnerAppCall := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CreatedAppID.Address(), + AccountData: expectedAppAccountData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: info.CreatedAppID + 1, + Addr: info.CreatedAppID.Address(), + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: innerProgramBytes, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, // #pragma version 6; int 1; + }, + }, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(info.CreatedAppID + 1): { + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CreatedAppID.Address(), + }, }, - }, ledgercore.AppLocalStateDelta{}) - expectedDeltaInnerAppCall.AddCreatable(basics.CreatableIndex(info.CreatedAppID+1), ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: info.CreatedAppID.Address(), - }) + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaInnerAppCall.Hydrate() expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw - expectedDeltaInnerPay1 := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) - expectedDeltaInnerPay1.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) - expectedDeltaInnerPay1.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + expectedDeltaInnerPay1 := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CreatedAppID.Address(), + AccountData: expectedAppAccountData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + }, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaInnerPay1.Hydrate() expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw - expectedDeltaInnerPay2 := ledgercore.MakeStateDelta(&info.BlockHeader, info.PrevTimestamp, 0, 0) - expectedDeltaInnerPay2.Accts.Upsert(info.CreatedAppID.Address(), expectedAppAccountData) - expectedDeltaInnerPay2.Accts.Upsert(info.FeeSinkAddr, expectedFeeSinkData) + expectedDeltaInnerPay2 := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CreatedAppID.Address(), + AccountData: expectedAppAccountData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + }, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaInnerPay2.Hydrate() return expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 } diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 10119b57bb..a7bd0f1b0c 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -17,12 +17,16 @@ package mocktracer import ( + "testing" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // EventType represents a type of logic.EvalTracer event @@ -218,3 +222,32 @@ func copyDeltas(deltas *ledgercore.StateDelta) *ledgercore.StateDelta { } return &clone } + +// AssertEventsEqual asserts that two slices of Events are equal, taking into account complex +// equality of StateDeltas. The arguments will be modified in-place to normalize any StateDeltas. +func AssertEventsEqual(t *testing.T, expected, actual []Event) { + t.Helper() + + // Dehydrate deltas for better comparison + for i := range expected { + if expected[i].Deltas != nil { + expected[i].Deltas.Dehydrate() + } + } + for i := range actual { + if actual[i].Deltas != nil { + actual[i].Deltas.Dehydrate() + } + } + + // These extra checks are not necessary for correctness, but they provide more targeted information on failure + if assert.Equal(t, len(expected), len(actual)) { + for i := range expected { + jsonExpectedDelta := protocol.EncodeJSONStrict(expected[i].Deltas) + jsonActualDelta := protocol.EncodeJSONStrict(actual[i].Deltas) + assert.Equal(t, expected[i].Deltas, actual[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expected[i].Type, actual[i].Type, jsonExpectedDelta, jsonActualDelta) + } + } + + require.Equal(t, expected, actual) +} diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 505892eaf9..663b65842e 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -243,21 +243,6 @@ func TestTransactionGroupWithTracer(t *testing.T) { scenarios := mocktracer.GetTestScenarios() - // TODO: remove this filter - // scenarios = map[string]mocktracer.TestScenarioGenerator{ - // "none": scenarios["none"], - // "before inners,error=true": scenarios["before inners,error=true"], - // "before inners,error=false": scenarios["before inners,error=false"], - // "first inner,error=true": scenarios["first inner,error=true"], - // "first inner,error=false": scenarios["first inner,error=false"], - // "between inners,error=true": scenarios["between inners,error=true"], - // "between inners,error=false": scenarios["between inners,error=false"], - // "second inner": scenarios["second inner"], - // "third inner": scenarios["third inner"], - // "after inners,error=true": scenarios["after inners,error=true"], - // "after inners,error=false": scenarios["after inners,error=false"], - // } - type tracerTestCase struct { name string firstTxnBehavior string @@ -337,7 +322,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { ApprovalProgram: fmt.Sprintf(`#pragma version 6 byte "hello" log - %s`, basicAppCallReturn), +%s`, basicAppCallReturn), ClearStateProgram: `#pragma version 6 int 1`, @@ -461,24 +446,48 @@ int 1`, expectedAcct0Data.TotalAppParams = 1 expectedBlockHeader := eval.block.BlockHeader - expectedBasicAppCallDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) - expectedBasicAppCallDelta.Accts.Upsert(addrs[0], expectedAcct0Data) - expectedBasicAppCallDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) - expectedBasicAppCallDelta.Accts.UpsertAppResource(addrs[0], 1, ledgercore.AppParamsDelta{ - Params: &basics.AppParams{ - ApprovalProgram: txgroup[0].Txn.ApprovalProgram, - ClearStateProgram: txgroup[0].Txn.ClearStateProgram, + expectedBasicAppCallDelta := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: addrs[0], + AccountData: expectedAcct0Data, + }, + { + Addr: testSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: 1, + Addr: addrs[0], + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: txgroup[0].Txn.ApprovalProgram, + ClearStateProgram: txgroup[0].Txn.ClearStateProgram, + }, + }, + }, + }, }, - }, ledgercore.AppLocalStateDelta{}) - expectedBasicAppCallDelta.AddCreatable(1, ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: addrs[0], - }) - expectedBasicAppCallDelta.Txids[txgroup[0].Txn.ID()] = ledgercore.IncludedTransactions{ - LastValid: txgroup[0].Txn.LastValid, - Intra: 0, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + 1: { + Ctype: basics.AppCreatable, + Created: true, + Creator: addrs[0], + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + txgroup[0].Txn.ID(): { + LastValid: txgroup[0].Txn.LastValid, + Intra: 0, + }, + }, + Hdr: &expectedBlockHeader, + PrevTimestamp: blkHeader.TimeStamp, } + expectedBasicAppCallDelta.Hydrate() expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} if testCase.firstTxnBehavior == "approve" { @@ -492,15 +501,37 @@ int 1`, expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw expectedFeeSinkData.MicroAlgos.Raw += txgroup[1].Txn.Fee.Raw - expectedPayTxnDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, blkHeader.TimeStamp, 0, 0) - expectedPayTxnDelta.Accts.Upsert(addrs[1], expectedAcct1Data) - expectedPayTxnDelta.Accts.Upsert(testSinkAddr, expectedFeeSinkData) - expectedPayTxnDelta.Accts.Upsert(addrs[2], expectedAcct2Data) - expectedPayTxnDelta.Accts.Upsert(addrs[3], expectedAcct3Data) - expectedPayTxnDelta.Txids[txgroup[1].Txn.ID()] = ledgercore.IncludedTransactions{ - LastValid: txgroup[1].Txn.LastValid, - Intra: 0, // will be incremented once merged + expectedPayTxnDelta := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: addrs[1], + AccountData: expectedAcct1Data, + }, + { + Addr: testSinkAddr, + AccountData: expectedFeeSinkData, + }, + { + Addr: addrs[2], + AccountData: expectedAcct2Data, + }, + { + Addr: addrs[3], + AccountData: expectedAcct3Data, + }, + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + txgroup[1].Txn.ID(): { + LastValid: txgroup[1].Txn.LastValid, + Intra: 0, // will be incremented once merged + }, + }, + Hdr: &expectedBlockHeader, + PrevTimestamp: blkHeader.TimeStamp, } + expectedPayTxnDelta.Hydrate() expectedDelta := mocktracer.MergeStateDeltas(expectedBasicAppCallDelta, expectedPayTxnDelta, scenario.ExpectedStateDelta) @@ -550,29 +581,7 @@ int 1`, })...) } actualEvents := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) - - // Dehydrate deltas for easier comparison - for i := range actualEvents { - if actualEvents[i].Deltas != nil { - actualEvents[i].Deltas.Dehydrate() - } - } - for i := range expectedEvents { - if expectedEvents[i].Deltas != nil { - expectedEvents[i].Deltas.Dehydrate() - } - } - - // These extra checks are not necessary for correctness, but they provide more targeted information on failure - if assert.Equal(t, len(expectedEvents), len(actualEvents)) { - for i := range expectedEvents { - jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) - jsonActualDelta := protocol.EncodeJSONStrict(actualEvents[i].Deltas) - assert.Equal(t, expectedEvents[i].Deltas, actualEvents[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expectedEvents[i].Type, actualEvents[i].Type, jsonExpectedDelta, jsonActualDelta) - } - } - - require.Equal(t, expectedEvents, actualEvents) + mocktracer.AssertEventsEqual(t, expectedEvents, actualEvents) }) } } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 9d07b73aea..0c183b7295 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -254,12 +254,23 @@ func MakeAccountDeltas(hint int) AccountDeltas { // Hydrate reverses the effects of Dehydrate, restoring internal data. func (ad *AccountDeltas) Hydrate() { + if ad.acctsCache == nil { + ad.acctsCache = make(map[basics.Address]int, len(ad.Accts)) + } for idx, acct := range ad.Accts { ad.acctsCache[acct.Addr] = idx } + + if ad.appResourcesCache == nil { + ad.appResourcesCache = make(map[AccountApp]int, len(ad.AppResources)) + } for idx, app := range ad.AppResources { ad.appResourcesCache[AccountApp{app.Addr, app.Aidx}] = idx } + + if ad.assetResourcesCache == nil { + ad.assetResourcesCache = make(map[AccountAsset]int, len(ad.AssetResources)) + } for idx, asset := range ad.AssetResources { ad.assetResourcesCache[AccountAsset{asset.Addr, asset.Aidx}] = idx } diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 385b64c8e0..fe2a17af48 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -32,7 +32,6 @@ import ( simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -170,23 +169,49 @@ int 1`, expectedBlockHeader.RewardsRate = evalBlock.RewardsRate expectedBlockHeader.RewardsResidue = evalBlock.RewardsResidue - expectedDelta := ledgercore.MakeStateDelta(&expectedBlockHeader, env.TxnInfo.LatestHeader.TimeStamp, 0, 0) - expectedDelta.Accts.Upsert(sender.Addr, expectedSenderData) - expectedDelta.Accts.Upsert(env.FeeSinkAccount.Addr, expectedFeeSinkData) - expectedDelta.Accts.Upsert(lsigAddr, expectedLsigData) - expectedDelta.Accts.UpsertAppResource(lsigAddr, expectedAppID, expectedAppParams, ledgercore.AppLocalStateDelta{}) - expectedDelta.AddCreatable(basics.CreatableIndex(expectedAppID), ledgercore.ModifiedCreatable{ - Ctype: basics.AppCreatable, - Created: true, - Creator: lsigAddr, - }) - expectedDelta.Txids[signedPayTxn.Txn.ID()] = ledgercore.IncludedTransactions{ - LastValid: signedPayTxn.Txn.LastValid, - Intra: 0, - } - expectedDelta.Txids[signedAppCallTxn.Txn.ID()] = ledgercore.IncludedTransactions{ - LastValid: signedAppCallTxn.Txn.LastValid, - Intra: 1, + expectedDelta := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: sender.Addr, + AccountData: expectedSenderData, + }, + { + Addr: env.FeeSinkAccount.Addr, + AccountData: expectedFeeSinkData, + }, + { + Addr: lsigAddr, + AccountData: expectedLsigData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: expectedAppID, + Addr: lsigAddr, + Params: expectedAppParams, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(expectedAppID): { + Ctype: basics.AppCreatable, + Created: true, + Creator: lsigAddr, + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + signedPayTxn.Txn.ID(): { + LastValid: signedPayTxn.Txn.LastValid, + Intra: 0, + }, + signedAppCallTxn.Txn.ID(): { + LastValid: signedAppCallTxn.Txn.LastValid, + Intra: 1, + }, + }, + Hdr: &expectedBlockHeader, + PrevTimestamp: env.TxnInfo.LatestHeader.TimeStamp, } expectedEvents := []mocktracer.Event{ @@ -210,28 +235,5 @@ int 1`, //Block evaluation mocktracer.AfterBlock(block.Block().Round()), } - actualEvents := mockTracer.Events - - // Dehydrate deltas for better comparison - for i := range expectedEvents { - if expectedEvents[i].Deltas != nil { - expectedEvents[i].Deltas.Dehydrate() - } - } - for i := range actualEvents { - if actualEvents[i].Deltas != nil { - actualEvents[i].Deltas.Dehydrate() - } - } - - // These extra checks are not necessary for correctness, but they provide more targeted information on failure - if assert.Equal(t, len(expectedEvents), len(actualEvents)) { - for i := range expectedEvents { - jsonExpectedDelta := protocol.EncodeJSONStrict(expectedEvents[i].Deltas) - jsonActualDelta := protocol.EncodeJSONStrict(actualEvents[i].Deltas) - assert.Equal(t, expectedEvents[i].Deltas, actualEvents[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expectedEvents[i].Type, actualEvents[i].Type, jsonExpectedDelta, jsonActualDelta) - } - } - - require.Equal(t, expectedEvents, actualEvents) + mocktracer.AssertEventsEqual(t, expectedEvents, mockTracer.Events) } From b2f9af1ef724b54f306f9d128f92ffd897e3a3b8 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 3 May 2023 17:04:07 -0700 Subject: [PATCH 18/26] Ledger unit tests --- ledger/eval/cow_test.go | 41 +++++++++++++ ledger/ledgercore/statedelta_test.go | 86 ++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index c57417818f..90cd38a57f 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -192,6 +192,47 @@ func TestCowBalance(t *testing.T) { checkCowByUpdate(t, c0, updates2) } +// TestCowDeltasAfterCommit tests that deltas are still valid after committing to parent. +func TestCowDeltasAfterCommit(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + accts0 := ledgertesting.RandomAccounts(20, true) + ml := mockLedger{balanceMap: accts0} + + c0 := makeRoundCowState( + &ml, bookkeeping.BlockHeader{}, config.Consensus[protocol.ConsensusCurrentVersion], + 0, ledgercore.AccountTotals{}, 0) + checkCow(t, c0, accts0) + + c1 := c0.child(0) + + acctUpdates, _, _ := ledgertesting.RandomDeltas(10, accts0, 0) + applyUpdates(c1, acctUpdates) + + c1.kvPut("key", []byte("value")) + expectedKvMods := map[string]ledgercore.KvValueDelta{ + "key": { + Data: []byte("value"), + }, + } + + actualDeltas := c1.deltas() + require.Equal(t, acctUpdates, actualDeltas.Accts) + require.Equal(t, expectedKvMods, actualDeltas.KvMods) + + // Parent should now have deltas + c1.commitToParent() + actualDeltas = c0.deltas() + require.Equal(t, acctUpdates, actualDeltas.Accts) + require.Equal(t, expectedKvMods, actualDeltas.KvMods) + + // Deltas remain valid in child after commit + actualDeltas = c0.deltas() + require.Equal(t, acctUpdates, actualDeltas.Accts) + require.Equal(t, expectedKvMods, actualDeltas.KvMods) +} + func BenchmarkCowChild(b *testing.B) { b.ReportAllocs() cow := makeRoundCowState(nil, bookkeeping.BlockHeader{}, config.ConsensusParams{}, 10000, ledgercore.AccountTotals{}, 16) diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index dd0f3e201d..6379237f85 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -99,6 +99,92 @@ func TestAccountDeltas(t *testing.T) { a.Equal(sample1, data) } +func TestAccountDeltasMergeAccountsOrder(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addr1 := randomAddress() + data1 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 111}}} + addr2 := randomAddress() + data2 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 222}}} + addr3 := randomAddress() + data3 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 333}}} + addr4 := randomAddress() + data4 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 444}}} + + asset1 := basics.AssetIndex(100) + asset1Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 1}, + } + asset2 := basics.AssetIndex(200) + asset2Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 2}, + } + asset3 := basics.AssetIndex(300) + asset3Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 3}, + } + asset4 := basics.AssetIndex(400) + asset4Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 4}, + } + + app1 := basics.AppIndex(101) + app1Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app1")}, + } + app2 := basics.AppIndex(201) + app2Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app2")}, + } + app3 := basics.AppIndex(301) + app3Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app3")}, + } + app4 := basics.AppIndex(401) + app4Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app4")}, + } + + var ad1 AccountDeltas + ad1.Upsert(addr1, data1) + ad1.Upsert(addr2, data2) + ad1.UpsertAssetResource(addr1, asset1, asset1Params, AssetHoldingDelta{}) + ad1.UpsertAssetResource(addr2, asset2, asset2Params, AssetHoldingDelta{}) + ad1.UpsertAppResource(addr1, app1, app1Params, AppLocalStateDelta{}) + ad1.UpsertAppResource(addr2, app2, app2Params, AppLocalStateDelta{}) + + var ad2 AccountDeltas + ad2.Upsert(addr3, data3) + ad2.Upsert(addr4, data4) + ad2.UpsertAssetResource(addr3, asset3, asset3Params, AssetHoldingDelta{}) + ad2.UpsertAssetResource(addr4, asset4, asset4Params, AssetHoldingDelta{}) + ad2.UpsertAppResource(addr3, app3, app3Params, AppLocalStateDelta{}) + ad2.UpsertAppResource(addr4, app4, app4Params, AppLocalStateDelta{}) + + // Iterate to ensure deterministic order + for i := 0; i < 10; i++ { + var merged AccountDeltas + merged.MergeAccounts(ad1) + merged.MergeAccounts(ad2) + + var expectedAccounts []BalanceRecord + expectedAccounts = append(expectedAccounts, ad1.Accts...) + expectedAccounts = append(expectedAccounts, ad2.Accts...) + require.Equal(t, expectedAccounts, merged.Accts) + + var expectedAppResources []AppResourceRecord + expectedAppResources = append(expectedAppResources, ad1.AppResources...) + expectedAppResources = append(expectedAppResources, ad2.AppResources...) + require.Equal(t, expectedAppResources, merged.AppResources) + + var expectedAssetResources []AssetResourceRecord + expectedAssetResources = append(expectedAssetResources, ad1.AssetResources...) + expectedAssetResources = append(expectedAssetResources, ad2.AssetResources...) + require.Equal(t, expectedAssetResources, merged.AssetResources) + } +} + func TestMakeStateDeltaMaps(t *testing.T) { partitiontest.PartitionTest(t) From 67cb81a1e634212c6c2f0328c97a159696c8c77d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 4 May 2023 10:05:34 -0700 Subject: [PATCH 19/26] Dehydrate deltas before comparison --- ledger/eval/cow_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index 90cd38a57f..3969c918ea 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -209,6 +209,7 @@ func TestCowDeltasAfterCommit(t *testing.T) { acctUpdates, _, _ := ledgertesting.RandomDeltas(10, accts0, 0) applyUpdates(c1, acctUpdates) + acctUpdates.Dehydrate() // Prep for comparison c1.kvPut("key", []byte("value")) expectedKvMods := map[string]ledgercore.KvValueDelta{ @@ -218,17 +219,20 @@ func TestCowDeltasAfterCommit(t *testing.T) { } actualDeltas := c1.deltas() + actualDeltas.Dehydrate() // Prep for comparison require.Equal(t, acctUpdates, actualDeltas.Accts) require.Equal(t, expectedKvMods, actualDeltas.KvMods) // Parent should now have deltas c1.commitToParent() actualDeltas = c0.deltas() + actualDeltas.Dehydrate() // Prep for comparison require.Equal(t, acctUpdates, actualDeltas.Accts) require.Equal(t, expectedKvMods, actualDeltas.KvMods) // Deltas remain valid in child after commit actualDeltas = c0.deltas() + actualDeltas.Dehydrate() // Prep for comparison require.Equal(t, acctUpdates, actualDeltas.Accts) require.Equal(t, expectedKvMods, actualDeltas.KvMods) } From 08eb749af6dfe0804db37c2a4be9cab004c6acb7 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 4 May 2023 11:59:07 -0700 Subject: [PATCH 20/26] Unit tests for hydrate/dehydrate --- ledger/ledgercore/statedelta.go | 6 +- ledger/ledgercore/statedelta_test.go | 252 ++++++++++++++++++++++++++- 2 files changed, 254 insertions(+), 4 deletions(-) diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 0c183b7295..c57d8c8a63 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -225,7 +225,9 @@ func (sd *StateDelta) Hydrate() { } // Dehydrate normalized the fields of this StateDelta, and clears any redundant internal caching. -// This is useful for comparing StateDelta objects during testing. +// This is useful for comparing StateDelta objects for equality. +// +// NOTE: initialHint is lost in dehydration. All other fields can be restored by calling Hydrate() func (sd *StateDelta) Dehydrate() { sd.Accts.Dehydrate() sd.initialHint = 0 @@ -277,7 +279,7 @@ func (ad *AccountDeltas) Hydrate() { } // Dehydrate normalized the fields of this AccountDeltas, and clears any redundant internal caching. -// This is useful for comparing AccountDeltas objects during testing. +// This is useful for comparing AccountDeltas objects for equality. func (ad *AccountDeltas) Dehydrate() { if ad.Accts == nil { ad.Accts = []BalanceRecord{} diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index 6379237f85..2e683a410d 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -20,6 +20,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/crypto" @@ -185,6 +186,253 @@ func TestAccountDeltasMergeAccountsOrder(t *testing.T) { } } +func TestAccountDeltasDehydrateAndHydrate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addr1 := randomAddress() + data1 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 111}}} + addr2 := randomAddress() + data2 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 222}}} + + asset1 := basics.AssetIndex(100) + asset1Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 1}, + } + asset2 := basics.AssetIndex(200) + asset2Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 2}, + } + + app1 := basics.AppIndex(101) + app1Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app1")}, + } + app2 := basics.AppIndex(201) + app2Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app2")}, + } + + var ad AccountDeltas + ad.Upsert(addr1, data1) + ad.Upsert(addr2, data2) + ad.UpsertAssetResource(addr1, asset1, asset1Params, AssetHoldingDelta{}) + ad.UpsertAssetResource(addr2, asset2, asset2Params, AssetHoldingDelta{}) + ad.UpsertAppResource(addr1, app1, app1Params, AppLocalStateDelta{}) + ad.UpsertAppResource(addr2, app2, app2Params, AppLocalStateDelta{}) + + var adCopy AccountDeltas + adCopy.Upsert(addr1, data1) + adCopy.Upsert(addr2, data2) + adCopy.UpsertAssetResource(addr1, asset1, asset1Params, AssetHoldingDelta{}) + adCopy.UpsertAssetResource(addr2, asset2, asset2Params, AssetHoldingDelta{}) + adCopy.UpsertAppResource(addr1, app1, app1Params, AppLocalStateDelta{}) + adCopy.UpsertAppResource(addr2, app2, app2Params, AppLocalStateDelta{}) + + shallowAd := AccountDeltas{ + Accts: []BalanceRecord{ + { + Addr: addr1, + AccountData: data1, + }, + { + Addr: addr2, + AccountData: data2, + }, + }, + acctsCache: make(map[basics.Address]int), + AssetResources: []AssetResourceRecord{ + { + Aidx: asset1, + Addr: addr1, + Params: asset1Params, + }, + { + Aidx: asset2, + Addr: addr2, + Params: asset2Params, + }, + }, + assetResourcesCache: make(map[AccountAsset]int), + AppResources: []AppResourceRecord{ + { + Aidx: app1, + Addr: addr1, + Params: app1Params, + }, + { + Aidx: app2, + Addr: addr2, + Params: app2Params, + }, + }, + appResourcesCache: make(map[AccountApp]int), + } + + require.Equal(t, adCopy, ad) // should be identical + require.NotEqual(t, shallowAd, ad) // shallowAd has empty internal fields + + ad.Dehydrate() + + // Dehydration empties the internal fields + require.Equal(t, shallowAd, ad) + require.NotEqual(t, adCopy, ad) + + ad.Hydrate() + + // Hydration restores the internal fields + require.Equal(t, adCopy, ad) + require.NotEqual(t, shallowAd, ad) + + t.Run("NewFieldDetection", func(t *testing.T) { + v := reflect.ValueOf(&ad).Elem() + st := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + structField := st.Field(i) + isContainer := field.Kind() == reflect.Map || field.Kind() == reflect.Slice + if isContainer || !structField.IsExported() { + assert.False(t, v.Field(i).IsZero(), "new container or private field \"%v\" added to AccountDeltas, please update AccountDeltas.Hydrate() and .Dehydrate() to handle it before fixing the test", structField.Name) + } + } + }) +} + +func TestStateDeltaDehydrateAndHydrate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addr := randomAddress() + data := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 111}}} + + asset := basics.AssetIndex(100) + assetParams := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 1}, + } + + app := basics.AppIndex(101) + appParams := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app1")}, + } + + prevTimestamp := int64(77) + stateProofNextRound := basics.Round(88) + var hdr bookkeeping.BlockHeader + + sd := MakeStateDelta(&hdr, prevTimestamp, 10, stateProofNextRound) + sd.Accts.Upsert(addr, data) + sd.Accts.UpsertAssetResource(addr, asset, assetParams, AssetHoldingDelta{}) + sd.Accts.UpsertAppResource(addr, app, appParams, AppLocalStateDelta{}) + sd.AddKvMod("key", KvValueDelta{Data: []byte("value")}) + sd.AddCreatable(100, ModifiedCreatable{ + Ctype: basics.AssetCreatable, + Created: true, + Creator: addr, + }) + sd.AddTxLease(Txlease{Sender: addr, Lease: [32]byte{1, 2, 3}}, 2000) + sd.Txids = map[transactions.Txid]IncludedTransactions{ + {5, 4, 3}: { + LastValid: 5, + }, + } + + sdCopy := MakeStateDelta(&hdr, prevTimestamp, 10, stateProofNextRound) + sdCopy.Accts.Upsert(addr, data) + sdCopy.Accts.UpsertAssetResource(addr, asset, assetParams, AssetHoldingDelta{}) + sdCopy.Accts.UpsertAppResource(addr, app, appParams, AppLocalStateDelta{}) + sdCopy.AddKvMod("key", KvValueDelta{Data: []byte("value")}) + sdCopy.AddCreatable(100, ModifiedCreatable{ + Ctype: basics.AssetCreatable, + Created: true, + Creator: addr, + }) + sdCopy.AddTxLease(Txlease{Sender: addr, Lease: [32]byte{1, 2, 3}}, 2000) + sdCopy.Txids = map[transactions.Txid]IncludedTransactions{ + {5, 4, 3}: { + LastValid: 5, + }, + } + + shallowSd := StateDelta{ + PrevTimestamp: prevTimestamp, + StateProofNext: stateProofNextRound, + Hdr: &hdr, + Accts: AccountDeltas{ + Accts: []BalanceRecord{ + { + Addr: addr, + AccountData: data, + }, + }, + acctsCache: make(map[basics.Address]int), + AssetResources: []AssetResourceRecord{ + { + Aidx: asset, + Addr: addr, + Params: assetParams, + }, + }, + assetResourcesCache: make(map[AccountAsset]int), + AppResources: []AppResourceRecord{ + { + Aidx: app, + Addr: addr, + Params: appParams, + }, + }, + appResourcesCache: make(map[AccountApp]int), + }, + KvMods: map[string]KvValueDelta{ + "key": {Data: []byte("value")}, + }, + Creatables: map[basics.CreatableIndex]ModifiedCreatable{ + 100: { + Ctype: basics.AssetCreatable, + Created: true, + Creator: addr, + }, + }, + Txleases: map[Txlease]basics.Round{ + {addr, [32]byte{1, 2, 3}}: 2000, + }, + Txids: map[transactions.Txid]IncludedTransactions{ + {5, 4, 3}: { + LastValid: 5, + }, + }, + } + + require.Equal(t, sdCopy, sd) // should be identical + require.NotEqual(t, shallowSd, sd) // shallowSd has empty internal fields + + sd.Dehydrate() + + // Dehydration empties the internal fields + require.Equal(t, shallowSd, sd) + require.NotEqual(t, sdCopy, sd) + + sd.Hydrate() + + // Hydration restores the internal fields, except for initialHint + require.NotEqual(t, sdCopy.initialHint, sd.initialHint) + sd.initialHint = sdCopy.initialHint + require.Equal(t, sdCopy, sd) + require.NotEqual(t, shallowSd, sd) + + t.Run("NewFieldDetection", func(t *testing.T) { + v := reflect.ValueOf(&sd).Elem() + st := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + structField := st.Field(i) + isContainer := field.Kind() == reflect.Map || field.Kind() == reflect.Slice + if isContainer || !structField.IsExported() { + assert.False(t, v.Field(i).IsZero(), "new container or private field \"%v\" added to StateDelta, please update StateDelta.Hydrate() and .Dehydrate() to handle it before fixing the test", structField.Name) + } + } + }) +} + func TestMakeStateDeltaMaps(t *testing.T) { partitiontest.PartitionTest(t) @@ -287,7 +535,7 @@ func TestStateDeltaReflect(t *testing.T) { st := v.Type() for i := 0; i < v.NumField(); i++ { reflectedStateDeltaName := st.Field(i).Name - require.Containsf(t, stateDeltaFieldNames, reflectedStateDeltaName, "new field:\"%v\" added to StateDelta, please update StateDelta.Reset() to handle it before fixing the test", reflectedStateDeltaName) + assert.Containsf(t, stateDeltaFieldNames, reflectedStateDeltaName, "new field:\"%v\" added to StateDelta, please update StateDelta.Reset() to handle it before fixing the test", reflectedStateDeltaName) } } @@ -308,7 +556,7 @@ func TestAccountDeltaReflect(t *testing.T) { st := v.Type() for i := 0; i < v.NumField(); i++ { reflectedAccountDeltaName := st.Field(i).Name - require.Containsf(t, AccountDeltaFieldNames, reflectedAccountDeltaName, "new field:\"%v\" added to AccountDeltas, please update AccountDeltas.reset() to handle it before fixing the test", reflectedAccountDeltaName) + assert.Containsf(t, AccountDeltaFieldNames, reflectedAccountDeltaName, "new field:\"%v\" added to AccountDeltas, please update AccountDeltas.reset() to handle it before fixing the test", reflectedAccountDeltaName) } } From ca3f65e91727f69dd801b72c4f335d472ab3d463 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 4 May 2023 15:01:06 -0700 Subject: [PATCH 21/26] Allocate new cow for granular eval in Perform --- ledger/eval/applications.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 7d7ee30989..47dcbced9a 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -299,7 +299,9 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgerco // In theory we could reuse this child cow allocation for all of the transactions in this // group; that's what BlockEvaluator.TransactionGroup does. However, achieving the same // thing here would require interface changes. - cowForTxn = cs.child(1) + + cowForTxn = &roundCowState{} // Specifically not calling cs.child(1), see comment below. + cs.reuseChild(cowForTxn, 1) defer func() { d := cowForTxn.deltas() deltas = &d @@ -307,7 +309,11 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgerco // cowForTxn.recycle() // DO NOT RECYCLE! // We cannot recycle here since we are returning the underlying StateDelta for this cow. // If we did recycle, there would be a race between the caller looking at the StateDelta - // and the next user of the recycled cow. + // and the next user of the recycled cow. For this reason, we allocate a new + // roundCowState above instead of using cs.child(); otherwise, we would be draining the + // underlying sync.Pool and causing other routines to do more allocation. The other + // routines are likely more important, since if GranularEval is enabled, that's a good + // indicator that evaluation is for debugging purposes. }() } From f87545e174b80bd9eaef45712cad3ae8293d4acd Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 8 May 2023 16:42:23 -0700 Subject: [PATCH 22/26] Undo individual txn deltas --- data/transactions/logic/eval.go | 12 +-- data/transactions/logic/ledger_test.go | 20 ++--- .../logic/mocktracer/scenarios.go | 74 ++++++++----------- data/transactions/logic/mocktracer/tracer.go | 8 +- data/transactions/logic/tracer.go | 8 +- data/transactions/logic/tracer_test.go | 3 - ledger/eval/applications.go | 57 +++++--------- ledger/eval/cow.go | 9 +-- ledger/eval/cow_test.go | 11 --- ledger/eval/eval.go | 32 +------- ledger/eval/eval_test.go | 51 ++++++------- ledger/simulation/simulation_eval_test.go | 22 +++--- ledger/simulation/simulator_test.go | 6 +- ledger/simulation/testing/utils.go | 8 +- ledger/simulation/tracer.go | 6 +- ledger/simulation/tracer_test.go | 5 +- 16 files changed, 120 insertions(+), 212 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 7d47e9626d..cbf93a9a9f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -238,7 +238,7 @@ type LedgerForLogic interface { SetBox(appIdx basics.AppIndex, key string, value []byte) error DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) (bool, error) - Perform(gi int, ep *EvalParams) (*ledgercore.StateDelta, error) + Perform(gi int, ep *EvalParams) error Counter() uint64 } @@ -283,11 +283,6 @@ type EvalParams struct { // optional tracer Tracer EvalTracer - // GranularEval controls whether we should create a child cow for each inner transaction. This - // is purely for debugging/tracing purposes, since this should have no effect on the actual - // result. - GranularEval bool - // MinAvmVersion is the minimum allowed AVM version of this program. // The program must reject if its version is less than this version. If // MinAvmVersion is nil, we will compute it ourselves @@ -467,7 +462,6 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) SigLedger: caller.SigLedger, Ledger: caller.Ledger, Tracer: caller.Tracer, - GranularEval: caller.GranularEval, MinAvmVersion: &minAvmVersion, FeeCredit: caller.FeeCredit, Specials: caller.Specials, @@ -5492,10 +5486,10 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxn(ep, i) } - deltas, err := cx.Ledger.Perform(i, ep) + err := cx.Ledger.Perform(i, ep) if ep.Tracer != nil { - ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, deltas, err) + ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, err) } if err != nil { diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 32a273d533..3016ae5298 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -870,33 +870,33 @@ func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnF } // Perform causes txn to "occur" against the ledger. -func (l *Ledger) Perform(gi int, ep *EvalParams) (*ledgercore.StateDelta, error) { +func (l *Ledger) Perform(gi int, ep *EvalParams) error { txn := &ep.TxnGroup[gi] err := l.move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee.Raw) if err != nil { - return nil, err + return err } err = l.rekey(&txn.Txn) if err != nil { - return nil, err + return err } switch txn.Txn.Type { case protocol.PaymentTx: - return nil, l.pay(txn.Txn.Sender, txn.Txn.PaymentTxnFields) + return l.pay(txn.Txn.Sender, txn.Txn.PaymentTxnFields) case protocol.AssetTransferTx: - return nil, l.axfer(txn.Txn.Sender, txn.Txn.AssetTransferTxnFields) + return l.axfer(txn.Txn.Sender, txn.Txn.AssetTransferTxnFields) case protocol.AssetConfigTx: - return nil, l.acfg(txn.Txn.Sender, txn.Txn.AssetConfigTxnFields, &txn.ApplyData) + return l.acfg(txn.Txn.Sender, txn.Txn.AssetConfigTxnFields, &txn.ApplyData) case protocol.AssetFreezeTx: - return nil, l.afrz(txn.Txn.Sender, txn.Txn.AssetFreezeTxnFields) + return l.afrz(txn.Txn.Sender, txn.Txn.AssetFreezeTxnFields) case protocol.ApplicationCallTx: - return nil, l.appl(txn.Txn.Sender, txn.Txn.ApplicationCallTxnFields, &txn.ApplyData, gi, ep) + return l.appl(txn.Txn.Sender, txn.Txn.ApplicationCallTxnFields, &txn.ApplyData, gi, ep) case protocol.KeyRegistrationTx: - return nil, nil // For now, presume success in test ledger + return nil // For now, presume success in test ledger default: - return nil, fmt.Errorf("%s txn in AVM", txn.Txn.Type) + return fmt.Errorf("%s txn in AVM", txn.Txn.Type) } } diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 0d10f2b21c..0b336db502 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -87,7 +87,6 @@ type TestScenarioInfo struct { FeeSinkAddr basics.Address MinFee basics.MicroAlgos CreatedAppID basics.AppIndex - GranularEval bool BlockHeader bookkeeping.BlockHeader PrevTimestamp int64 } @@ -381,7 +380,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), + AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -390,16 +389,16 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), false), + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, StateDeltaIfTrue(expectedDeltaInnerPay2, info.GranularEval), false), + AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, false), AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedAD, StateDeltaIfTrue(expectedDelta, info.GranularEval), false), + AfterTxn(protocol.ApplicationCallTx, expectedAD, false), }, }), ExpectedSimulationAD: expectedAD, @@ -438,7 +437,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn - // remove failed txids from granular delta + // remove failed txids from delta expectedDeltaCallingTxn.Txids = nil // EvalDeltas are removed from failed app call transactions @@ -460,7 +459,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(4, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, @@ -475,12 +474,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { firstInnerName := fmt.Sprintf("first inner,error=%t", shouldError) firstInner := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", failureInnerProgram, "", 1, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, _, _ := expectedApplyDataAndStateDelta(info, program, failureInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, failureInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn - // remove failed txids from granular delta + // remove failed txids from delta expectedDeltaCallingTxn.Txids = nil - expectedDeltaInnerAppCall.Txids = nil // EvalDeltas are removed from failed app call transactions expectedInnerAppCallADNoEvalDelta := expectedAD.EvalDelta.InnerTxns[0].ApplyData @@ -513,11 +511,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, true), AfterTxnGroup(1, nil, true), // end first itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, @@ -532,10 +530,10 @@ func GetTestScenarios() map[string]TestScenarioGenerator { betweenInnersName := fmt.Sprintf("between inners,error=%t", shouldError) betweenInners := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, failureOps, 1, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn - // remove failed txids from granular delta + // remove failed txids from delta expectedDeltaCallingTxn.Txids = nil expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData @@ -568,14 +566,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(4, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, @@ -591,12 +589,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { secondInnerName := "second inner" secondInner := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", math.MaxUint64, 2, "pushint 1") - expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn - // remove failed txids from granular delta + // remove failed txids from delta expectedDeltaCallingTxn.Txids = nil - expectedDeltaInnerPay1.Txids = nil expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -629,7 +626,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -638,11 +635,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), true), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, true), AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, @@ -657,12 +654,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { thirdInnerName := "third inner" thirdInner := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", 1, math.MaxUint64, "pushint 1") - expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn - // remove failed txids from granular delta + // remove failed txids from delta expectedDeltaCallingTxn.Txids = nil - expectedDeltaInnerPay2.Txids = nil expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -696,7 +692,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -705,13 +701,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), false), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, StateDeltaIfTrue(expectedDeltaInnerPay2, info.GranularEval), true), + AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, true), AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, @@ -727,10 +723,10 @@ func GetTestScenarios() map[string]TestScenarioGenerator { afterInnersName := fmt.Sprintf("after inners,error=%t", shouldError) afterInners := func(info TestScenarioInfo) TestScenario { program := fillProgramTemplate("", successInnerProgram, "", 1, 2, singleFailureOp) - expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) expectedDelta := expectedDeltaCallingTxn - // remove failed txids from granular delta + // remove failed txids from delta expectedDeltaCallingTxn.Txids = nil expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData @@ -759,7 +755,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { OpcodeEvents(3, false), { AfterProgram(logic.ModeApp, false), - AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, StateDeltaIfTrue(expectedDeltaInnerAppCall, info.GranularEval), false), + AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, @@ -768,16 +764,16 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeOpcode(), BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, StateDeltaIfTrue(expectedDeltaInnerPay1, info.GranularEval), false), + AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, false), BeforeTxn(protocol.PaymentTx), - AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, StateDeltaIfTrue(expectedDeltaInnerPay2, info.GranularEval), false), + AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, false), AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, shouldError), { AfterProgram(logic.ModeApp, shouldError), - AfterTxn(protocol.ApplicationCallTx, expectedADNoED, StateDeltaIfTrue(expectedDeltaCallingTxn, info.GranularEval), true), + AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, @@ -845,11 +841,3 @@ func MergeStateDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { } return result } - -// StateDeltaIfTrue returns a pointer to the given delta if the given condition is true, or nil -func StateDeltaIfTrue(delta ledgercore.StateDelta, cond bool) *ledgercore.StateDelta { - if cond { - return &delta - } - return nil -} diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index a7bd0f1b0c..c79ed80e24 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -107,8 +107,8 @@ func BeforeTxn(txnType protocol.TxType) Event { } // AfterTxn creates a new Event with the type AfterTxnEvent -func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData, deltas *ledgercore.StateDelta, hasError bool) Event { - return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad, Deltas: copyDeltas(deltas), HasError: hasError} +func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData, hasError bool) Event { + return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad, HasError: hasError} } // AfterProgram creates a new Event with the type AfterProgramEvent @@ -179,8 +179,8 @@ func (d *Tracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { } // AfterTxn mocks the logic.EvalTracer.AfterTxn method -func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { - d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad, deltas, evalError != nil)) +func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { + d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad, evalError != nil)) } // BeforeProgram mocks the logic.EvalTracer.BeforeProgram method diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index e916f5b6ea..4b4c5f5807 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -145,11 +145,7 @@ type EvalTracer interface { // 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. - // - // If GranularEval is enabled by the evaluator (specified either in logic.EvalParams or ledger's - // BlockEvaluator), deltas is the ledgercore.StateDelta changes that occurred because of this - // transaction. Otherwise, this argument is nil. - AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) + AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) // BeforeProgram is called before an app or LogicSig program is evaluated. BeforeProgram(cx *EvalContext) @@ -185,7 +181,7 @@ func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDe func (n NullEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) {} // AfterTxn does nothing -func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { +func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { } // BeforeProgram does nothing diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go index 1b85358df3..9e5f9564b3 100644 --- a/data/transactions/logic/tracer_test.go +++ b/data/transactions/logic/tracer_test.go @@ -143,9 +143,6 @@ func TestInnerAppEvalWithTracer(t *testing.T) { mock := mocktracer.Tracer{} ep, tx, ledger := MakeSampleEnv() ep.Tracer = &mock - // Note: it does not make sense to test ep.GranularEval=true here, - // since these tests use a testing Ledger, which does not use cows - // or StateDeltas internally. // Establish FirstTestID as the app id, and fund it. We do this so that the created // inner app will get a sequential ID, which is what the mocktracer scenarios expect diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 47dcbced9a..e126cfd309 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -292,42 +292,18 @@ func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basi return true, cs.kvDel(fullKey) } -func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgercore.StateDelta, err error) { - cowForTxn := cs - - if ep.GranularEval { - // In theory we could reuse this child cow allocation for all of the transactions in this - // group; that's what BlockEvaluator.TransactionGroup does. However, achieving the same - // thing here would require interface changes. - - cowForTxn = &roundCowState{} // Specifically not calling cs.child(1), see comment below. - cs.reuseChild(cowForTxn, 1) - defer func() { - d := cowForTxn.deltas() - deltas = &d - cowForTxn.commitToParent() - // cowForTxn.recycle() // DO NOT RECYCLE! - // We cannot recycle here since we are returning the underlying StateDelta for this cow. - // If we did recycle, there would be a race between the caller looking at the StateDelta - // and the next user of the recycled cow. For this reason, we allocate a new - // roundCowState above instead of using cs.child(); otherwise, we would be draining the - // underlying sync.Pool and causing other routines to do more allocation. The other - // routines are likely more important, since if GranularEval is enabled, that's a good - // indicator that evaluation is for debugging purposes. - }() - } - +func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { txn := &ep.TxnGroup[gi] // move fee to pool - err = cowForTxn.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) + err := cs.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) if err != nil { - return + return err } - err = apply.Rekey(cowForTxn, &txn.Txn) + err = apply.Rekey(cs, &txn.Txn) if err != nil { - return + return err } // compared to eval.transaction() it may seem strange that we @@ -343,29 +319,32 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgerco switch txn.Txn.Type { case protocol.PaymentTx: - err = apply.Payment(txn.Txn.PaymentTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData) + err = apply.Payment(txn.Txn.PaymentTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) case protocol.KeyRegistrationTx: - err = apply.Keyreg(txn.Txn.KeyregTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData, - cowForTxn.Round()) + err = apply.Keyreg(txn.Txn.KeyregTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData, + cs.Round()) case protocol.AssetConfigTx: - err = apply.AssetConfig(txn.Txn.AssetConfigTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData, - cowForTxn.Counter()) + err = apply.AssetConfig(txn.Txn.AssetConfigTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData, + cs.Counter()) case protocol.AssetTransferTx: - err = apply.AssetTransfer(txn.Txn.AssetTransferTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData) + err = apply.AssetTransfer(txn.Txn.AssetTransferTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) case protocol.AssetFreezeTx: - err = apply.AssetFreeze(txn.Txn.AssetFreezeTxnFields, txn.Txn.Header, cowForTxn, *ep.Specials, &txn.ApplyData) + err = apply.AssetFreeze(txn.Txn.AssetFreezeTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) case protocol.ApplicationCallTx: - err = apply.ApplicationCall(txn.Txn.ApplicationCallTxnFields, txn.Txn.Header, cowForTxn, &txn.ApplyData, - gi, ep, cowForTxn.Counter()) + err = apply.ApplicationCall(txn.Txn.ApplicationCallTxnFields, txn.Txn.Header, cs, &txn.ApplyData, + gi, ep, cs.Counter()) default: err = fmt.Errorf("%s tx in AVM", txn.Txn.Type) } + if err != nil { + return err + } // We don't check min balances during in app txns. @@ -373,5 +352,5 @@ func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) (deltas *ledgerco // top-level txn concludes, because cow will return all changed accounts in // modifiedAccounts(). - return + return nil } diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index d4a7fba364..c58f65fc51 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -271,14 +271,6 @@ func (cb *roundCowState) SetStateProofNextRound(rnd basics.Round) { func (cb *roundCowState) child(hint int) *roundCowState { ch := childPool.Get().(*roundCowState) - cb.reuseChild(ch, hint) - return ch -} - -// reuseChild creates a new child of this roundCowState, reusing the provided child object. If the -// child object was previously used, the caller MUST ensure ch.reset() is called before invoking -// this function. -func (cb *roundCowState) reuseChild(ch *roundCowState, hint int) { ch.lookupParent = cb ch.commitParent = cb ch.proto = cb.proto @@ -294,6 +286,7 @@ func (cb *roundCowState) reuseChild(ch *roundCowState, hint int) { ch.compatibilityGetKeyCache = make(map[basics.Address]map[storagePtr]uint64) } } + return ch } func (cb *roundCowState) commitToParent() { diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index 3969c918ea..df33df168e 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -246,17 +246,6 @@ func BenchmarkCowChild(b *testing.B) { } } -func BenchmarkCowReuseChild(b *testing.B) { - b.ReportAllocs() - cow := makeRoundCowState(nil, bookkeeping.BlockHeader{}, config.ConsensusParams{}, 10000, ledgercore.AccountTotals{}, 16) - calf := cow.child(1) - calf.reset() - for i := 0; i < b.N; i++ { - cow.reuseChild(calf, 16) - calf.reset() - } -} - // Ideally we'd be able to randomize the roundCowState but can't do it via reflection // since it' can't set unexported fields. This test just makes sure that all of the existing // fields are correctly reset but won't be able to catch any new fields added. diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 7c49cc8999..914d7cdca2 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -600,11 +600,6 @@ type BlockEvaluator struct { maxTxnBytesPerBlock int - // GranularEval controls whether the evaluator should create a child cow for each transaction. - // This is purely for debugging/tracing purposes, since this should have no effect on the actual - // result. - GranularEval bool - Tracer logic.EvalTracer } @@ -964,7 +959,6 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit defer cow.recycle() evalParams := logic.NewEvalParams(txgroup, &eval.proto, &eval.specials) - evalParams.GranularEval = eval.GranularEval evalParams.Tracer = eval.Tracer if eval.Tracer != nil { @@ -978,39 +972,17 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit // Evaluate each transaction in the group txibs = make([]transactions.SignedTxnInBlock, 0, len(txgroup)) - cowForTxn := cow for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock - if eval.GranularEval { - if gi == 0 { - cowForTxn = cow.child(1) - defer cowForTxn.recycle() - } else { - // Reuse the same cow allocation for all transactions in this group - cowForTxn.reset() - cow.reuseChild(cowForTxn, 1) - } - } - if eval.Tracer != nil { eval.Tracer.BeforeTxn(evalParams, gi) } - err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cowForTxn, &txib) + err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cow, &txib) if eval.Tracer != nil { - var deltas *ledgercore.StateDelta - if eval.GranularEval { - // Only include if we're sure the cowForTxn contains ONLY the deltas from this txn - d := cowForTxn.deltas() - deltas = &d - } - eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, deltas, err) - } - - if eval.GranularEval { - cowForTxn.commitToParent() + eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData, err) } if err != nil { diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index b8600c2123..6eaeed4037 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -247,29 +247,25 @@ func TestTransactionGroupWithTracer(t *testing.T) { name string firstTxnBehavior string innerAppCallScenario mocktracer.TestScenarioGenerator - granularEval bool } var testCases []tracerTestCase - for _, granularEval := range []bool{false, true} { - firstIteration := true - for scenarioName, scenario := range scenarios { - firstTxnBehaviors := []string{"approve"} - if firstIteration { - // When the first transaction rejects or errors, the behavior of the later transactions - // don't matter, so we only want to test these cases with any one mocktracer scenario. - firstTxnBehaviors = append(firstTxnBehaviors, "reject", "error") - firstIteration = false - } + firstIteration := true + for scenarioName, scenario := range scenarios { + firstTxnBehaviors := []string{"approve"} + if firstIteration { + // When the first transaction rejects or errors, the behavior of the later transactions + // don't matter, so we only want to test these cases with any one mocktracer scenario. + firstTxnBehaviors = append(firstTxnBehaviors, "reject", "error") + firstIteration = false + } - for _, firstTxnTxnBehavior := range firstTxnBehaviors { - testCases = append(testCases, tracerTestCase{ - name: fmt.Sprintf("firstTxnBehavior=%s,scenario=%s,granularEval=%t", firstTxnTxnBehavior, scenarioName, granularEval), - firstTxnBehavior: firstTxnTxnBehavior, - innerAppCallScenario: scenario, - granularEval: granularEval, - }) - } + for _, firstTxnTxnBehavior := range firstTxnBehaviors { + testCases = append(testCases, tracerTestCase{ + name: fmt.Sprintf("firstTxnBehavior=%s,scenario=%s", firstTxnTxnBehavior, scenarioName), + firstTxnBehavior: firstTxnTxnBehavior, + innerAppCallScenario: scenario, + }) } } @@ -279,6 +275,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { t.Parallel() genesisInitState, addrs, keys := ledgertesting.Genesis(10) + basicAppID := basics.AppIndex(1001) innerAppID := basics.AppIndex(1003) innerAppAddress := innerAppID.Address() balances := genesisInitState.Accounts @@ -300,7 +297,6 @@ func TestTransactionGroupWithTracer(t *testing.T) { require.NoError(t, err) eval.validate = true eval.generate = true - eval.GranularEval = testCase.granularEval genHash := l.GenesisHash() @@ -372,7 +368,6 @@ int 1`, FeeSinkAddr: testSinkAddr, MinFee: minFee, CreatedAppID: innerAppID, - GranularEval: testCase.granularEval, BlockHeader: eval.block.BlockHeader, PrevTimestamp: blkHeader.TimeStamp, }) @@ -425,7 +420,7 @@ int 1`, } expectedBasicAppCallAD := transactions.ApplyData{ - ApplicationID: 1001, + ApplicationID: basicAppID, EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, LocalDeltas: map[uint64]basics.StateDelta{}, @@ -460,7 +455,7 @@ int 1`, }, AppResources: []ledgercore.AppResourceRecord{ { - Aidx: 1, + Aidx: basicAppID, Addr: addrs[0], Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ @@ -472,7 +467,7 @@ int 1`, }, }, Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ - 1: { + basics.CreatableIndex(basicAppID): { Ctype: basics.AppCreatable, Created: true, Creator: addrs[0], @@ -549,9 +544,9 @@ int 1`, mocktracer.OpcodeEvents(3, false), { mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, testCase.granularEval), false), // end basicAppCallTxn - mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn - mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, mocktracer.StateDeltaIfTrue(expectedPayTxnDelta, testCase.granularEval), false), // end payTxn + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, false), // end basicAppCallTxn + mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn + mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, false), // end payTxn }, scenario.ExpectedEvents, { @@ -575,7 +570,7 @@ int 1`, mocktracer.OpcodeEvents(3, hasError), { mocktracer.AfterProgram(logic.ModeApp, hasError), - mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, mocktracer.StateDeltaIfTrue(expectedBasicAppCallDelta, testCase.granularEval), true), // end basicAppCallTxn + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, true), // end basicAppCallTxn mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true), }, })...) diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index e5bffaeeae..91bc5a844c 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -121,7 +121,7 @@ func validateSimulationResult(t *testing.T, result simulation.Result) { func simulationTest(t *testing.T, f func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase) { t.Helper() env := simulationtesting.PrepareSimulatorTest(t) - defer env.Ledger.Close() + defer env.Close() s := simulation.MakeSimulator(env.Ledger) testcase := f(env.Accounts, env.TxnInfo) @@ -368,7 +368,7 @@ func TestStateProofTxn(t *testing.T) { t.Parallel() env := simulationtesting.PrepareSimulatorTest(t) - defer env.Ledger.Close() + defer env.Close() s := simulation.MakeSimulator(env.Ledger) txgroup := []transactions.SignedTxn{ @@ -387,7 +387,7 @@ func TestSimpleGroupTxn(t *testing.T) { t.Parallel() env := simulationtesting.PrepareSimulatorTest(t) - defer env.Ledger.Close() + defer env.Close() s := simulation.MakeSimulator(env.Ledger) sender1 := env.Accounts[0] sender1Balance := env.Accounts[0].AcctData.MicroAlgos @@ -1002,15 +1002,15 @@ func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { ` + strings.Repeat(`int 1; pop;`, 700) + `end: int 1` - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) - sender := accounts[0] + sender := env.Accounts[0] futureAppID := basics.AppIndex(1) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -1018,7 +1018,7 @@ func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { ClearStateProgram: `#pragma version 6; int 0`, }) // Expensive 700 repetition of int 1 and pop total cost 1404 - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1211,7 +1211,7 @@ func TestDefaultSignatureCheck(t *testing.T) { t.Parallel() env := simulationtesting.PrepareSimulatorTest(t) - defer env.Ledger.Close() + defer env.Close() s := simulation.MakeSimulator(env.Ledger) sender := env.Accounts[0] @@ -1633,7 +1633,7 @@ func TestOptionalSignaturesIncorrect(t *testing.T) { t.Parallel() env := simulationtesting.PrepareSimulatorTest(t) - defer env.Ledger.Close() + defer env.Close() s := simulation.MakeSimulator(env.Ledger) sender := env.Accounts[0] diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index fe2a17af48..eba1b194c2 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -107,7 +107,7 @@ func TestSimulateWithTrace(t *testing.T) { t.Parallel() env := simulationtesting.PrepareSimulatorTest(t) - defer env.Ledger.Close() + defer env.Close() s := MakeSimulator(env.Ledger) sender := env.Accounts[0] @@ -224,13 +224,13 @@ int 1`, mocktracer.BeforeBlock(block.Block().Round()), mocktracer.BeforeTxnGroup(2), mocktracer.BeforeTxn(protocol.PaymentTx), - mocktracer.AfterTxn(protocol.PaymentTx, evalBlock.Payset[0].ApplyData, nil, false), + mocktracer.AfterTxn(protocol.PaymentTx, evalBlock.Payset[0].ApplyData, false), mocktracer.BeforeTxn(protocol.ApplicationCallTx), mocktracer.BeforeProgram(logic.ModeApp), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, evalBlock.Payset[1].ApplyData, nil, false), + mocktracer.AfterTxn(protocol.ApplicationCallTx, evalBlock.Payset[1].ApplyData, false), mocktracer.AfterTxnGroup(2, &expectedDelta, false), //Block evaluation mocktracer.AfterBlock(block.Block().Round()), diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index e15811008c..2d7003bcfd 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -86,7 +86,13 @@ type Environment struct { TxnInfo TxnInfo } -// PrepareSimulatorTest creates an environment to test transaction simulations +// Close reclaims resources used by the testing environment +func (env *Environment) Close() { + env.Ledger.Close() +} + +// PrepareSimulatorTest creates an environment to test transaction simulations. The caller is +// responsible for calling Close() on the returned environment. func PrepareSimulatorTest(t *testing.T) Environment { genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 750269716e..2b30a2e7f8 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -44,7 +44,7 @@ func (tracer *cursorEvalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) tracer.previousInnerTxns = append(tracer.previousInnerTxns, 0) } -func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { +func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { tracer.previousInnerTxns = tracer.previousInnerTxns[:len(tracer.previousInnerTxns)-1] } @@ -163,10 +163,10 @@ func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { applyDataOfCurrentTxn.EvalDelta = evalDelta } -func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, deltas *ledgercore.StateDelta, evalError error) { +func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { tracer.handleError(evalError) tracer.saveApplyData(ad) - tracer.cursorEvalTracer.AfterTxn(ep, groupIndex, ad, deltas, evalError) + tracer.cursorEvalTracer.AfterTxn(ep, groupIndex, ad, evalError) } func (tracer *evalTracer) saveEvalDelta(evalDelta transactions.EvalDelta, appIDToSave basics.AppIndex) { diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index 89c3faef5b..190739e73c 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -17,7 +17,6 @@ package simulation import ( - "fmt" "testing" "github.com/algorand/go-algorand/data/transactions" @@ -219,7 +218,7 @@ func TestCursorEvalTracer(t *testing.T) { for _, tc := range testCases { tc := tc - t.Run(fmt.Sprintf("%s", tc.name), func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { t.Parallel() var tracer cursorEvalTracer @@ -232,7 +231,7 @@ func TestCursorEvalTracer(t *testing.T) { case mocktracer.BeforeTxnEvent: tracer.BeforeTxn(&ep, groupIndex) case mocktracer.AfterTxnEvent: - tracer.AfterTxn(&ep, groupIndex, transactions.ApplyData{}, nil, nil) + tracer.AfterTxn(&ep, groupIndex, transactions.ApplyData{}, nil) case mocktracer.BeforeTxnGroupEvent: tracer.BeforeTxnGroup(&ep) case mocktracer.AfterTxnGroupEvent: From d7cc4b984c393da5aa9daf4fc212a8709cc883d4 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 8 May 2023 16:53:07 -0700 Subject: [PATCH 23/26] Use Environment struct in simulation eval tests --- ledger/simulation/simulation_eval_test.go | 270 +++++++++++----------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 91bc5a844c..87944a887e 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -118,13 +118,13 @@ func validateSimulationResult(t *testing.T, result simulation.Result) { } } -func simulationTest(t *testing.T, f func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase) { +func simulationTest(t *testing.T, f func(env simulationtesting.Environment) simulationTestCase) { t.Helper() env := simulationtesting.PrepareSimulatorTest(t) defer env.Close() s := simulation.MakeSimulator(env.Ledger) - testcase := f(env.Accounts, env.TxnInfo) + testcase := f(env) actual, err := s.Simulate(testcase.input) require.NoError(t, err) @@ -161,11 +161,11 @@ func TestPayTxn(t *testing.T) { t.Run("simple", func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, @@ -178,7 +178,7 @@ func TestPayTxn(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -191,13 +191,13 @@ func TestPayTxn(t *testing.T) { t.Run("close to", func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] - closeTo := accounts[2] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] + closeTo := env.Accounts[2] amount := uint64(1_000_000) - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, @@ -214,7 +214,7 @@ func TestPayTxn(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -235,12 +235,12 @@ func TestPayTxn(t *testing.T) { t.Run("overspend", func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] amount := sender.AcctData.MicroAlgos.Raw + 100 - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, @@ -254,7 +254,7 @@ func TestPayTxn(t *testing.T) { expectedError: fmt.Sprintf("tried to spend {%d}", amount), expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -274,11 +274,11 @@ func TestWrongAuthorizerTxn(t *testing.T) { optionalSigs := optionalSigs t.Run(fmt.Sprintf("optionalSigs=%t", optionalSigs), func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - authority := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + authority := env.Accounts[1] - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -298,7 +298,7 @@ func TestWrongAuthorizerTxn(t *testing.T) { expectedError: fmt.Sprintf("should have been authorized by %s but was actually authorized by %s", sender.Addr, authority.Addr), expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -318,18 +318,18 @@ func TestWrongAuthorizerTxn(t *testing.T) { func TestRekey(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - authority := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + authority := env.Accounts[1] - txn1 := txnInfo.NewTxn(txntest.Txn{ + txn1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, Amount: 1, RekeyTo: authority.Addr, }) - txn2 := txnInfo.NewTxn(txntest.Txn{ + txn2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -349,7 +349,7 @@ func TestRekey(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -500,16 +500,16 @@ btoi`) testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: lsigAddr, Amount: 1_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: lsigAddr, ApprovalProgram: `#pragma version 8 @@ -554,7 +554,7 @@ int 1`, expectedError: testCase.expectedError, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -582,12 +582,12 @@ int 1`, func TestSimpleAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] // Create program and call it futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -607,7 +607,7 @@ int 1 int 0 `, }) - callTxn := txnInfo.NewTxn(txntest.Txn{ + callTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -626,7 +626,7 @@ int 0 }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -664,11 +664,11 @@ int 0 func TestRejectAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -690,7 +690,7 @@ int 0 expectedError: "transaction rejected by ApprovalProgram", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -719,11 +719,11 @@ int 0 func TestErrorAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -745,7 +745,7 @@ int 0 expectedError: "err opcode executed", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -784,12 +784,12 @@ func TestAppCallOverBudget(t *testing.T) { `, 697) + `end: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -800,7 +800,7 @@ int 0 }) // App call with cost 1398 - will cause a budget exceeded error, // but will only report a cost up to 1396. - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -820,7 +820,7 @@ int 0 expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -857,12 +857,12 @@ func TestAppCallWithExtraBudget(t *testing.T) { ` + strings.Repeat(`int 1; pop;`, 700) + `end: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -870,7 +870,7 @@ func TestAppCallWithExtraBudget(t *testing.T) { ClearStateProgram: `#pragma version 6; int 0`, }) // Expensive 700 repetition of int 1 and pop total cost 1404 - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -891,7 +891,7 @@ func TestAppCallWithExtraBudget(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -928,12 +928,12 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { ` + strings.Repeat(`int 1; pop;`, 700) + `end: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -941,7 +941,7 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { ClearStateProgram: `#pragma version 6; int 0`, }) // Expensive 700 repetition of int 1 and pop total cost 1404 - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -964,7 +964,7 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1055,16 +1055,16 @@ pop program := logic.Program(op.Program) lsigAddr := basics.Address(crypto.HashObj(&program)) - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: lsigAddr, Amount: 1_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: lsigAddr, ApprovalProgram: `#pragma version 8 @@ -1096,7 +1096,7 @@ int 1`, expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1140,19 +1140,19 @@ itxn_submit pop `, 345)) - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(2) // fund outer app - fund := txnInfo.NewTxn(txntest.Txn{ + fund := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureAppID.Address(), Amount: 401_000, }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApprovalProgram: exactly700AndCallInner, @@ -1173,7 +1173,7 @@ int 1`, }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1252,10 +1252,10 @@ func TestDefaultSignatureCheck(t *testing.T) { func TestInvalidTxGroup(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - receiver := accounts[0].Addr + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + receiver := env.Accounts[0].Addr - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, // should error with invalid transaction group error Sender: ledgertesting.PoolAddr(), @@ -1270,7 +1270,7 @@ func TestInvalidTxGroup(t *testing.T) { expectedError: "transaction from incentive pool is invalid", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { FailedAt: simulation.TxnPath{0}, @@ -1301,13 +1301,13 @@ log `, LogLongLine), LogTimes) + `final: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -1315,7 +1315,7 @@ int 1` ClearStateProgram: "#pragma version 8\nint 1", }) - callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + callsABunchLogs := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1342,7 +1342,7 @@ int 1` }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1395,13 +1395,13 @@ log `, LogLongLine), LogTimes) + `final: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -1409,7 +1409,7 @@ int 1` ClearStateProgram: "#pragma version 8\nint 1", }) - callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + callsABunchLogs := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1437,7 +1437,7 @@ int 1` }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { FailedAt: simulation.TxnPath{1}, @@ -1480,15 +1480,15 @@ int 1` func TestBalanceChangesWithApp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] senderBalance := sender.AcctData.MicroAlgos.Raw sendAmount := senderBalance - 500_000 // Leave 0.5 Algos in the sender account - receiver := accounts[1] + receiver := env.Accounts[1] receiverBalance := receiver.AcctData.MicroAlgos.Raw futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApprovalProgram: `#pragma version 6 @@ -1506,20 +1506,20 @@ int 1 // [1] ClearStateProgram: `#pragma version 6 int 1`, }) - checkStartingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ + checkStartingBalanceTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, Accounts: []basics.Address{receiver.Addr}, ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance)}, }) - paymentTxn := txnInfo.NewTxn(txntest.Txn{ + paymentTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, Amount: sendAmount, }) - checkEndingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ + checkEndingBalanceTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1548,7 +1548,7 @@ int 1`, }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1585,10 +1585,10 @@ func TestOptionalSignatures(t *testing.T) { for _, signed := range []bool{true, false} { signed := signed t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -1610,7 +1610,7 @@ func TestOptionalSignatures(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -1656,10 +1656,10 @@ func TestOptionalSignaturesIncorrect(t *testing.T) { func TestPartialMissingSignatures(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - txn1 := txnInfo.NewTxn(txntest.Txn{ + txn1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.AssetConfigTx, Sender: sender.Addr, AssetParams: basics.AssetParams{ @@ -1669,7 +1669,7 @@ func TestPartialMissingSignatures(t *testing.T) { UnitName: "A", }, }) - txn2 := txnInfo.NewTxn(txntest.Txn{ + txn2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.AssetConfigTx, Sender: sender.Addr, AssetParams: basics.AssetParams{ @@ -1695,7 +1695,7 @@ func TestPartialMissingSignatures(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1732,23 +1732,23 @@ func TestPartialMissingSignatures(t *testing.T) { func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender1 := accounts[0] - sender2 := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender1 := env.Accounts[0] + sender2 := env.Accounts[1] - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender1.Addr, Receiver: sender2.Addr, Amount: 1_000_000, - Fee: txnInfo.CurrentProtocolParams().MinTxnFee - 100, + Fee: env.TxnInfo.CurrentProtocolParams().MinTxnFee - 100, }) - pay2 := txnInfo.NewTxn(txntest.Txn{ + pay2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender2.Addr, Receiver: sender1.Addr, Amount: 0, - Fee: txnInfo.CurrentProtocolParams().MinTxnFee + 100, + Fee: env.TxnInfo.CurrentProtocolParams().MinTxnFee + 100, }) txntest.Group(&pay1, &pay2) @@ -1766,7 +1766,7 @@ func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1830,28 +1830,28 @@ func TestAppCallInnerTxnApplyDataOnFail(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] singleInnerLogAndFail := makeProgramToCallInner(t, logAndFail) nestedInnerLogAndFail := makeProgramToCallInner(t, singleInnerLogAndFail) // fund outer app - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: basics.AppIndex(3).Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // fund inner app - pay2 := txnInfo.NewTxn(txntest.Txn{ + pay2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: basics.AppIndex(4).Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationArgs: [][]byte{uint64ToBytes(uint64(1))}, @@ -1873,7 +1873,7 @@ int 1`, expectedError: "rejected by ApprovalProgram", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1937,21 +1937,21 @@ func TestNonAppCallInnerTxnApplyDataOnFail(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] logAndFailItxnCode := makeItxnSubmitToCallInner(t, logAndFail) approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + logAndFailItxnCode) // fund outer app - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: basics.AppIndex(2).Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationArgs: [][]byte{uint64ToBytes(uint64(1))}, @@ -1973,7 +1973,7 @@ int 1`, expectedError: "rejected by ApprovalProgram", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -2032,21 +2032,21 @@ log func TestInnerTxnNonAppCallFailure(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] // configAssetCode should fail because createAssetCode does not set an asset manager approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + fmt.Sprintf(configAssetCode, 3)) // fund outer app - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: basics.AppIndex(2).Address(), Amount: 402_000, // 400_000 min balance plus 2_000 for 2 inners }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationArgs: [][]byte{uint64ToBytes(uint64(1))}, @@ -2068,7 +2068,7 @@ int 1`, expectedError: "logic eval error: this transaction should be issued by the manager", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -2113,17 +2113,17 @@ func TestMockTracerScenarios(t *testing.T) { scenarioFn := scenarioFn t.Run(name, func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(2) - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureAppID.Address(), Amount: 2_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ClearStateProgram: `#pragma version 6 @@ -2131,7 +2131,7 @@ func TestMockTracerScenarios(t *testing.T) { }) scenario := scenarioFn(mocktracer.TestScenarioInfo{ CallingTxn: appCallTxn.Txn(), - MinFee: basics.MicroAlgos{Raw: txnInfo.CurrentProtocolParams().MinTxnFee}, + MinFee: basics.MicroAlgos{Raw: env.TxnInfo.CurrentProtocolParams().MinTxnFee}, CreatedAppID: futureAppID, }) appCallTxn.ApprovalProgram = scenario.Program @@ -2148,7 +2148,7 @@ func TestMockTracerScenarios(t *testing.T) { } expected := simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { AppBudgetAdded: scenario.AppBudgetAdded, From f544699176425b6db3fec1234e33ffa3e3359c17 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 9 May 2023 11:11:13 -0700 Subject: [PATCH 24/26] Fix test expected block header --- ledger/simulation/simulator_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index eba1b194c2..90eada3f42 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -166,8 +166,10 @@ int 1`, // Cannot use evalBlock directly because the tracer is called before many block details are finalized expectedBlockHeader := bookkeeping.MakeBlock(env.TxnInfo.LatestHeader).BlockHeader expectedBlockHeader.TimeStamp = evalBlock.TimeStamp - expectedBlockHeader.RewardsRate = evalBlock.RewardsRate + expectedBlockHeader.RewardsLevel = evalBlock.RewardsLevel expectedBlockHeader.RewardsResidue = evalBlock.RewardsResidue + expectedBlockHeader.RewardsRate = evalBlock.RewardsRate + expectedBlockHeader.RewardsRecalculationRound = evalBlock.RewardsRecalculationRound expectedDelta := ledgercore.StateDelta{ Accts: ledgercore.AccountDeltas{ From f39564cb4935359401d37e2f72106a77d17dc492 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 11 May 2023 14:46:33 -0700 Subject: [PATCH 25/26] Use pointers in MergeAccount loops --- ledger/ledgercore/statedelta.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index c57d8c8a63..7e9e25e0d0 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -442,13 +442,16 @@ func (ad AccountDeltas) ModifiedAccounts() []basics.Address { // MergeAccounts applies other accounts into this StateDelta accounts func (ad *AccountDeltas) MergeAccounts(other AccountDeltas) { - for _, balanceRecord := range other.Accts { + for i := range other.Accts { + balanceRecord := &other.Accts[i] ad.Upsert(balanceRecord.Addr, balanceRecord.AccountData) } - for _, appResource := range other.AppResources { + for i := range other.AppResources { + appResource := &other.AppResources[i] ad.UpsertAppResource(appResource.Addr, appResource.Aidx, appResource.Params, appResource.State) } - for _, assetResource := range other.AssetResources { + for i := range other.AssetResources { + assetResource := &other.AssetResources[i] ad.UpsertAssetResource(assetResource.Addr, assetResource.Aidx, assetResource.Params, assetResource.Holding) } } From 762966bdda9e5b0ad3236103a3558ec194b31ba2 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 12 May 2023 10:52:23 -0700 Subject: [PATCH 26/26] Remove JSON printing --- data/transactions/logic/mocktracer/tracer.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index c79ed80e24..13a6d92d7a 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -243,9 +243,7 @@ func AssertEventsEqual(t *testing.T, expected, actual []Event) { // These extra checks are not necessary for correctness, but they provide more targeted information on failure if assert.Equal(t, len(expected), len(actual)) { for i := range expected { - jsonExpectedDelta := protocol.EncodeJSONStrict(expected[i].Deltas) - jsonActualDelta := protocol.EncodeJSONStrict(actual[i].Deltas) - assert.Equal(t, expected[i].Deltas, actual[i].Deltas, "StateDelta disagreement: i=%d, event type: (%v,%v)\n\nexpected: %s\n\nactual: %s", i, expected[i].Type, actual[i].Type, jsonExpectedDelta, jsonActualDelta) + assert.Equal(t, expected[i].Deltas, actual[i].Deltas, "StateDelta disagreement: i=%d, expected event type: %v, actual event type: %v", i, expected[i].Type, actual[i].Type) } }