Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pool App call budget in group transactions #2711

Merged
merged 15 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ type ConsensusParams struct {
// each Txn has a MinFee.
EnableFeePooling bool

// EnableAppCostPooling specifies that the sum of fees for application calls
// in a group is checked against the sum of the budget for application calls,
// rather than check each individual app call is within the budget.
EnableAppCostPooling bool

// RewardUnit specifies the number of MicroAlgos corresponding to one reward
// unit.
//
Expand Down Expand Up @@ -1005,6 +1010,9 @@ func initConsensusProtocols() {
// Enable TEAL 5 / AVM 1.0
vFuture.LogicSigVersion = 5

// Enable App calls to pool budget in grouped transactions
vFuture.EnableAppCostPooling = true

Consensus[protocol.ConsensusFuture] = vFuture
}

Expand Down
1 change: 0 additions & 1 deletion data/transactions/logic/TEAL_opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ Ops have a 'cost' of 1 unless otherwise specified.
- Pushes: uint64
- for (data A, signature B, pubkey C) verify the signature of ("ProgData" || program_hash || data) against the pubkey => {0 or 1}
- **Cost**: 1900
- Mode: Signature

The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.

Expand Down
13 changes: 12 additions & 1 deletion data/transactions/logic/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ type EvalParams struct {

// determines eval mode: runModeSignature or runModeApplication
runModeFlags runMode

// Total pool of app call budget in a group transaction
PooledApplicationBudget *uint64
}

type opEvalFunc func(cx *evalContext)
Expand Down Expand Up @@ -263,6 +266,9 @@ func (ep EvalParams) budget() int {
if ep.runModeFlags == runModeSignature {
return int(ep.Proto.LogicSigMaxCost)
}
if ep.Proto.EnableAppCostPooling && ep.PooledApplicationBudget != nil {
return int(*ep.PooledApplicationBudget)
}
return ep.Proto.MaxAppProgramCost
}

Expand Down Expand Up @@ -365,6 +371,10 @@ func EvalStateful(program []byte, params EvalParams) (pass bool, err error) {
cx.EvalParams = params
cx.runModeFlags = runModeApplication
pass, err = eval(program, &cx)
if cx.EvalParams.Proto.EnableAppCostPooling && cx.EvalParams.PooledApplicationBudget != nil {
// if eval passes, then budget is always greater than cost, so should not have underflow
*cx.EvalParams.PooledApplicationBudget = basics.SubSaturate(*cx.EvalParams.PooledApplicationBudget, uint64(cx.cost))
}

// set side effects
cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch)
Expand Down Expand Up @@ -640,7 +650,8 @@ func (cx *evalContext) step() {
}
cx.cost += deets.Cost
if cx.cost > cx.budget() {
cx.err = fmt.Errorf("pc=%3d dynamic cost budget of %d exceeded, executing %s", cx.pc, cx.budget(), spec.Name)
cx.err = fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: remaining budget is %d but program cost was %d",
cx.pc, spec.Name, cx.budget(), cx.cost)
return
}

Expand Down
48 changes: 46 additions & 2 deletions data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,15 +697,33 @@ log
})
}

// check ed25519verify and arg are not allowed in statefull mode
disallowed := []string{
// check that ed25519verify and arg is not allowed in stateful mode between v2-v4
disallowedV4 := []string{
"byte 0x01\nbyte 0x01\nbyte 0x01\ned25519verify",
"arg 0",
"arg_0",
"arg_1",
"arg_2",
"arg_3",
}
for _, source := range disallowedV4 {
ops := testProg(t, source, 4)
ep := defaultEvalParams(nil, nil)
err := CheckStateful(ops.Program, ep)
require.Error(t, err)
_, err = EvalStateful(ops.Program, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "not allowed in current mode")
}

// check that arg is not allowed in stateful mode beyond v5
disallowed := []string{
"arg 0",
"arg_0",
"arg_1",
"arg_2",
"arg_3",
}
for _, source := range disallowed {
ops := testProg(t, source, AssemblerMaxVersion)
ep := defaultEvalParams(nil, nil)
Expand Down Expand Up @@ -2929,3 +2947,29 @@ bnz loop
require.Empty(t, delta.LocalDeltas)
require.Len(t, delta.Logs, 29)
}

func TestPooledAppCallsVerifyOp(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

source := `#pragma version 5
global CurrentApplicationID
pop
byte 0x01
byte "ZC9KNzlnWTlKZ1pwSkNzQXVzYjNBcG1xTU9YbkRNWUtIQXNKYVk2RzRBdExPakQx"
addr DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE
ed25519verify
pop
int 1`

ep, _ := makeSampleEnv()
ep.Proto.EnableAppCostPooling = true
ep.PooledApplicationBudget = new(uint64)
// Simulate test with 2 grouped txn
*ep.PooledApplicationBudget = uint64(ep.Proto.MaxAppProgramCost * 2)
testApp(t, source, ep, "pc=107 dynamic cost budget exceeded, executing ed25519verify: remaining budget is 1400 but program cost was 1905")

// Simulate test with 3 grouped txn
*ep.PooledApplicationBudget = uint64(ep.Proto.MaxAppProgramCost * 3)
testApp(t, source, ep)
}
1 change: 1 addition & 0 deletions data/transactions/logic/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ var OpSpecs = []OpSpec{
{0x03, "sha512_256", opSHA512_256, asmDefault, disDefault, oneBytes, oneBytes, 2, modeAny, costly(45)},

{0x04, "ed25519verify", opEd25519verify, asmDefault, disDefault, threeBytes, oneInt, 1, runModeSignature, costly(1900)},
{0x04, "ed25519verify", opEd25519verify, asmDefault, disDefault, threeBytes, oneInt, 5, modeAny, costly(1900)},
{0x08, "+", opPlus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault},
{0x09, "-", opMinus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault},
{0x0a, "/", opDiv, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault},
Expand Down
1 change: 1 addition & 0 deletions ledger/apply/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ func (b *testBalancesPass) Put(addr basics.Address, ad basics.AccountData) error
func (b *testBalancesPass) ConsensusParams() config.ConsensusParams {
return b.proto
}

func (b *testBalancesPass) Allocate(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error {
b.allocatedAppIdx = aidx
return nil
Expand Down
19 changes: 13 additions & 6 deletions ledger/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,12 +672,18 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi
var groupNoAD []transactions.SignedTxn
var pastSideEffects []logic.EvalSideEffects
var minTealVersion uint64
pooledApplicationBudget := uint64(0)
res = make([]*logic.EvalParams, len(txgroup))
for i, txn := range txgroup {
// Ignore any non-ApplicationCall transactions
if txn.SignedTxn.Txn.Type != protocol.ApplicationCallTx {
continue
}
if eval.proto.EnableAppCostPooling {
pooledApplicationBudget += uint64(eval.proto.MaxAppProgramCost)
} else {
pooledApplicationBudget = uint64(eval.proto.MaxAppProgramCost)
}

// Initialize side effects and group without ApplyData lazily
if groupNoAD == nil {
Expand All @@ -690,12 +696,13 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi
}

res[i] = &logic.EvalParams{
Txn: &groupNoAD[i],
Proto: &eval.proto,
TxnGroup: groupNoAD,
GroupIndex: i,
PastSideEffects: pastSideEffects,
MinTealVersion: &minTealVersion,
Txn: &groupNoAD[i],
Proto: &eval.proto,
TxnGroup: groupNoAD,
GroupIndex: i,
PastSideEffects: pastSideEffects,
MinTealVersion: &minTealVersion,
PooledApplicationBudget: &pooledApplicationBudget,
}
}
return
Expand Down
Loading