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

Dryrun: Split dryrun cost field into BudgetConsumed and BudgetAdded #3957

Merged
merged 20 commits into from
May 26, 2022
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
6 changes: 6 additions & 0 deletions cmd/goal/clerk.go
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,12 @@ var dryrunRemoteCmd = &cobra.Command{
if txnResult.Cost != nil {
fmt.Fprintf(os.Stdout, "tx[%d] cost: %d\n", i, *txnResult.Cost)
}
if txnResult.BudgetConsumed != nil {
fmt.Fprintf(os.Stdout, "tx[%d] budget consumed: %d\n", i, *txnResult.BudgetConsumed)
}
if txnResult.BudgetAdded != nil {
fmt.Fprintf(os.Stdout, "tx[%d] budget added: %d\n", i, *txnResult.BudgetAdded)
}

fmt.Fprintf(os.Stdout, "tx[%d] messages:\n", i)
for _, msg := range msgs {
Expand Down
10 changes: 9 additions & 1 deletion daemon/algod/api/algod.oas2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2295,8 +2295,16 @@
"format": "byte"
}
},
"budget-added": {
"description": "Budget added during execution of app call transaction.",
"type": "integer"
},
"budget-consumed": {
"description": "Budget consumed during execution of app call transaction.",
"type": "integer"
},
"cost": {
"description": "Execution cost of app call transaction",
"description": "Net cost of app execution. Field is DEPRECATED and is subject for removal. Instead, use `budget-added` and `budget-consumed.",
"type": "integer"
}
}
Expand Down
10 changes: 9 additions & 1 deletion daemon/algod/api/algod.oas3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1328,8 +1328,16 @@
},
"type": "array"
},
"budget-added": {
"description": "Budget added during execution of app call transaction.",
"type": "integer"
},
"budget-consumed": {
"description": "Budget consumed during execution of app call transaction.",
"type": "integer"
},
"cost": {
"description": "Execution cost of app call transaction",
"description": "Net cost of app execution. Field is DEPRECATED and is subject for removal. Instead, use `budget-added` and `budget-consumed.",
"type": "integer"
},
"disassembly": {
Expand Down
23 changes: 21 additions & 2 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,17 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
err = fmt.Errorf("cost budget exceeded: budget is %d but program cost was %d", allowedBudget-cumulativeCost, cost)
}
}
cost64 := uint64(cost)
result.Cost = &cost64
// The cost is broken up into two fields: budgetAdded and budgetConsumed.
// This is necessary because the fields can only be represented as unsigned
// integers, so a negative cost would underflow. The two fields also provide
// more information, which can be useful for testing purposes.
// cost = budgetConsumed - budgetAdded
netCost := uint64(cost)
budgetAdded := uint64(proto.MaxAppProgramCost * numInnerTxns(delta))
budgetConsumed := uint64(cost) + budgetAdded
result.Cost = &netCost
result.BudgetAdded = &budgetAdded
result.BudgetConsumed = &budgetConsumed
maxCurrentBudget = pooledAppBudget
cumulativeCost += cost

Expand Down Expand Up @@ -624,3 +633,13 @@ func MergeAppParams(base *basics.AppParams, update *basics.AppParams) {
base.GlobalStateSchema = update.GlobalStateSchema
}
}

// count all inner transactions contained within the eval delta
func numInnerTxns(delta transactions.EvalDelta) (cnt int) {
cnt = len(delta.InnerTxns)
for _, itxn := range delta.InnerTxns {
cnt += numInnerTxns(itxn.EvalDelta)
}

return
}
65 changes: 53 additions & 12 deletions daemon/algod/api/server/v2/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1260,27 +1260,47 @@ func TestDryrunCost(t *testing.T) {
msg string
numHashes int
}{
{"REJECT", 12},
{"PASS", 5},
{"REJECT", 22},
{"PASS", 16},
}

for _, test := range tests {
t.Run(test.msg, func(t *testing.T) {
costs := make([]uint64, 2)
expectedCosts := make([]int64, 3)
expectedBudgetAdded := make([]uint64, 3)

ops, err := logic.AssembleString("#pragma version 5\nbyte 0x41\n" + strings.Repeat("keccak256\n", test.numHashes) + "pop\nint 1\n")
require.NoError(t, err)
approval := ops.Program
costs[0] = 3 + uint64(test.numHashes)*130
app1 := ops.Program
expectedCosts[0] = 3 + int64(test.numHashes)*130
expectedBudgetAdded[0] = 0

ops, err = logic.AssembleString("int 1")
require.NoError(t, err)
clst := ops.Program

ops, err = logic.AssembleString("#pragma version 5 \nint 1 \nint 2 \npop")
require.NoError(t, err)
approv := ops.Program
costs[1] = 3
app2 := ops.Program
expectedCosts[1] = 3
expectedBudgetAdded[1] = 0

ops, err = logic.AssembleString(`#pragma version 6
itxn_begin
int appl
itxn_field TypeEnum
int DeleteApplication
itxn_field OnCompletion
byte 0x068101 // #pragma version 6; int 1;
itxn_field ApprovalProgram
byte 0x068101 // #pragma version 6; int 1;
itxn_field ClearStateProgram
itxn_submit
int 1`)
require.NoError(t, err)
app3 := ops.Program
expectedCosts[2] = -687
expectedBudgetAdded[2] = 700

var appIdx basics.AppIndex = 1
creator := randomAddress()
Expand All @@ -1307,13 +1327,23 @@ func TestDryrunCost(t *testing.T) {
},
},
},
{
Txn: transactions.Transaction{
Header: transactions.Header{Sender: sender},
Type: protocol.ApplicationCallTx,
ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
ApplicationID: appIdx + 2,
OnCompletion: transactions.OptInOC,
},
},
},
},
Apps: []generated.Application{
{
Id: uint64(appIdx),
Params: generated.ApplicationParams{
Creator: creator.String(),
ApprovalProgram: approval,
ApprovalProgram: app1,
ClearStateProgram: clst,
LocalStateSchema: &generated.ApplicationStateSchema{NumByteSlice: 1},
},
Expand All @@ -1322,7 +1352,16 @@ func TestDryrunCost(t *testing.T) {
Id: uint64(appIdx + 1),
Params: generated.ApplicationParams{
Creator: creator.String(),
ApprovalProgram: approv,
ApprovalProgram: app2,
ClearStateProgram: clst,
LocalStateSchema: &generated.ApplicationStateSchema{NumByteSlice: 1},
},
},
{
Id: uint64(appIdx + 2),
Params: generated.ApplicationParams{
Creator: creator.String(),
ApprovalProgram: app3,
ClearStateProgram: clst,
LocalStateSchema: &generated.ApplicationStateSchema{NumByteSlice: 1},
},
Expand All @@ -1340,13 +1379,15 @@ func TestDryrunCost(t *testing.T) {
var response generated.DryrunResponse
doDryrunRequest(&dr, &response)
require.Empty(t, response.Error)
require.Equal(t, 2, len(response.Txns))
require.Equal(t, 3, len(response.Txns))

for i, txn := range response.Txns {
messages := *txn.AppCallMessages
require.GreaterOrEqual(t, len(messages), 1)
require.NotNil(t, *txn.Cost)
require.Equal(t, costs[i], *txn.Cost)
cost := int64(*txn.BudgetConsumed) - int64(*txn.BudgetAdded)
require.NotNil(t, cost)
require.Equal(t, expectedCosts[i], cost)
require.Equal(t, expectedBudgetAdded[i], *txn.BudgetAdded)
statusMatches := false
costExceedFound := false
for _, msg := range messages {
Expand Down
Loading