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

simulate: resource population #6015

Draft
wants to merge 26 commits into
base: master
Choose a base branch
from

Conversation

joe-p
Copy link
Contributor

@joe-p joe-p commented Jun 5, 2024

Summary

When a user calls simulate with UnnamedResources enabled, simulate should suggest to the user how they can populate the resource arrays in their transactions to properly send the transaction group to the network.

Test Plan

  • Test ResourcePopulator works with simple local (not group sharing) resources
  • Test ResourcePopulator with group sharing
  • Test ResourcePopulator resource limit detection with group sharing (ie. it is able to find the correct transaction to put a resource in)
  • Test Simulate with ResourcePopulator functionality
  • Test /simulate endpoint with ResourcePopulator functionality
  • Write smaller tests for better ledger/simulation/resources.go coverage

@joe-p joe-p changed the title Feat/populate_resources resource population Jun 5, 2024
@joe-p joe-p changed the title resource population simulate: resource population Jun 5, 2024
Copy link

codecov bot commented Jun 6, 2024

Codecov Report

Attention: Patch coverage is 76.13636% with 63 lines in your changes missing coverage. Please review.

Project coverage is 55.97%. Comparing base (97ab559) to head (ec7a36a).
Report is 10 commits behind head on master.

Current head ec7a36a differs from pull request most recent head 351f915

Please upload reports for the commit 351f915 to get more accurate results.

Files Patch % Lines
ledger/simulation/resources.go 76.58% 42 Missing and 17 partials ⚠️
ledger/simulation/simulator.go 66.66% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6015      +/-   ##
==========================================
+ Coverage   55.88%   55.97%   +0.09%     
==========================================
  Files         482      482              
  Lines       68571    68835     +264     
==========================================
+ Hits        38320    38531     +211     
- Misses      27646    27680      +34     
- Partials     2605     2624      +19     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@joe-p
Copy link
Contributor Author

joe-p commented Jul 24, 2024

In 99abd2f I have added resource population to the simulate API but for some reason this struct is not being properly encoded in the response

type PopulatedResourceArrays struct {
	Accounts []basics.Address    `codec:"accounts"`
	Assets   []basics.AssetIndex `codec:"assets"`
	Apps     []basics.AppIndex   `codec:"apps"`
	Boxes    []logic.BoxRef      `codec:"boxes"`
}

Boxes is encoded properly, but Accounts, Assets, and Apps are coming up nil. This behavior can be seen when running TestSimulateWithUnnamedResources with the changes in this commit. This seems to be an issue with the handler or encoder because TestPopulateResources shows simulate itself returns references (in this case addresses) as expected.

@jasonpaulos Any idea where the data is getting lost? In general, what is the best way to debug these algod API tests?

@@ -576,6 +576,7 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedS
AppBudgetAdded: omitEmpty(txnGroupResult.AppBudgetAdded),
AppBudgetConsumed: omitEmpty(txnGroupResult.AppBudgetConsumed),
UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed),
PopulatedResourceArrays: txnGroupResult.PopulatedResourceArrays,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs omitEmpty?

@@ -3948,6 +3948,10 @@
"fix-signers": {
"description": "If true, signers for transactions that are missing signatures will be fixed during evaluation.",
"type": "boolean"
},
"populate-resource-arrays": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer we say "populate-resources" here. I don't think we need to mention the datatype in the name. (This will also lead to some smaller diffs below).

ExtraOpcodeBudget uint64 `codec:"extra-opcode-budget,omitempty"`
ExecTraceConfig simulation.ExecTraceConfig `codec:"exec-trace-config,omitempty"`
FixSigners bool `codec:"fix-signers,omitempty"`
PopulateResourceArrays bool `codec:"populate-resource-arrays,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an example of where "PopulateResources" will make for a smaller diff.

ExtraOpcodeBudget: request.ExtraOpcodeBudget,
TraceConfig: request.ExecTraceConfig,
FixSigners: request.FixSigners,
PopulateResourceArrays: request.PopulateResourceArrays,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another potentially smaller diff.

// The static fields are resource arrays that were given in the txn group and thus cannot be removed
// The assumption is that these are prefilled because of one of the following reaons:
// - This transaction has already been signed
// - One of the foreign arrays is accessed on-chain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reads funny. Do you mean this?

Suggested change
// - One of the foreign arrays is accessed on-chain
// - One of the foreign resources is accessed on-chain

return nil
}
}
return fmt.Errorf("no room for holding")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More info usually better:

Suggested change
return fmt.Errorf("no room for holding")
return fmt.Errorf("no room for holding %s : %d", addr, aid)

Lots of other similar.

}

func (r *txnResources) addApp(aid basics.AppIndex) {
r.apps[aid] = struct{}{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to track that this adds availability of the app account too.

Suggested change
r.apps[aid] = struct{}{}
r.apps[aid] = struct{}{}
r.accounts[aid.Address()] = struct{}{}

}

for _, app := range txn.ForeignApps {
p.txnResources[groupIndex].staticApps[app] = struct{}{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App account becomes accessible too.

Suggested change
p.txnResources[groupIndex].staticApps[app] = struct{}{}
p.txnResources[groupIndex].staticApps[app] = struct{}{}
p.txnResources[groupIndex].staticAccounts[app.Address()] = struct{}{}

return hasField || hasStatic || hasRef
}

func (r *txnResources) addAccount(addr basics.Address) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the style of needing to call hasRoom() and then invoking add* unconditionally. Could the add* methods return error instead?
This would require you have two forms of some add* methods, or an extra argument that means "add this if you can do so by adding only one reference".

Comment on lines +735 to +740
for asset := range r.assets {
assets = append(assets, asset)
}
for asset := range r.staticAssets {
assets = append(assets, asset)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can use the ... form of append.

Copy link
Contributor

@jasonpaulos jasonpaulos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally submitting my review that's been in progress for a while. Note there's some overlap with what @jannotti has already posted

@@ -4265,6 +4269,10 @@
},
"unnamed-resources-accessed": {
"$ref": "#/definitions/SimulateUnnamedResourcesAccessed"
},
"populated-resource-arrays": {
"description": "Present if populate-resource-arrays is true in the request. In that case, it will be a map of transaction index in the group to populated resource arrays. There may be moure resource arrays given than transaction in the group, which means more app call transactions would be needed for extra resources.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"description": "Present if populate-resource-arrays is true in the request. In that case, it will be a map of transaction index in the group to populated resource arrays. There may be moure resource arrays given than transaction in the group, which means more app call transactions would be needed for extra resources.",
"description": "Present if populate-resource-arrays is true in the request. In that case, it will be a map of transaction index in the group to populated resource arrays. There may be more resource arrays given than transaction in the group, which means more app call transactions would be needed for extra resources.",

},
"populated-resource-arrays": {
"description": "Present if populate-resource-arrays is true in the request. In that case, it will be a map of transaction index in the group to populated resource arrays. There may be moure resource arrays given than transaction in the group, which means more app call transactions would be needed for extra resources.",
"type": "object"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there is a very compelling reason, I would recommend against using a map with non-string keys in the response object. It makes JSON encoding much more difficult.

I'd instead suggest an array, or (my preference) adding this as a property to individual SimulateTransactionResult objects, with another array here for any extra txns.

@@ -605,3 +605,488 @@ func (p *resourcePolicy) AvailableBox(app basics.AppIndex, name string, operatio
}
return p.tracker.addBox(app, name, readSize, *p.initialBoxSurplusReadBudget, p.ep.Proto.BytesPerBoxReference)
}

// txnResources tracks the resources being added to a tranasction during resource population
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// txnResources tracks the resources being added to a tranasction during resource population
// txnResources tracks the resources being added to a transaction during resource population

// txnResources tracks the resources being added to a tranasction during resource population
type txnResources struct {
// The static fields are resource arrays that were given in the txn group and thus cannot be removed
// The assumption is that these are prefilled because of one of the following reaons:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The assumption is that these are prefilled because of one of the following reaons:
// The assumption is that these are prefilled because of one of the following reasons:

staticAssets map[basics.AssetIndex]struct{}
staticApps map[basics.AppIndex]struct{}
staticAccounts map[basics.Address]struct{}
staticBoxes []logic.BoxRef
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI you can use logic.BoxRef as a map key

accounts: make(map[basics.Address]struct{}),
boxes: []logic.BoxRef{},
maxTotalRefs: consensusParams.MaxAppTotalTxnReferences,
maxAccounts: consensusParams.MaxAppTxnAccounts,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you choosing to ignore the individual limits on assets, apps, and boxes because they happen to be the same as the total ref limit? I'd rather not have this code rely on that always being true

}

// PopulatedResourceArrays is a struct that contains all the populated arrays for a txn
type PopulatedResourceArrays struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with the other structs in this package, I would prefer this struct not have codec tags and be directly exposed through the REST API. Instead a new model should be added to the oas2 spec and then daemon/algod/api/server/v2/utils.go can convert between this simulation.PopulatedResourceArrays and the generated model struct.

FailureMessage *string `codec:"failure-message,omitempty"`
UnnamedResourcesAccessed *model.SimulateUnnamedResourcesAccessed `codec:"unnamed-resources-accessed,omitempty"`
Txns []PreEncodedSimulateTxnResult `codec:"txn-results"`
PopulatedResourceArrays map[int]simulation.PopulatedResourceArrays `code:"populated-resource-arrays,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PopulatedResourceArrays map[int]simulation.PopulatedResourceArrays `code:"populated-resource-arrays,omitempty"`
PopulatedResourceArrays map[int]simulation.PopulatedResourceArrays `codec:"populated-resource-arrays,omitempty"`

},
},
}
a.Equal(expectedResult, resp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: the test failure that you're seeing. This is what the server sends back (decoded from the msgpack response):

"unnamed-resources-accessed": {
  "app-locals": [
    {
      "account": "7WCFW7UO7NA3NTQWDPKMFT4NNTOU4A7M5IF5VGPB3OCH6TMLD4X6QX453M",
      "app": 1005
    }
  ],
  "asset-holdings": [
    {
      "account": "7WCFW7UO7NA3NTQWDPKMFT4NNTOU4A7M5IF5VGPB3OCH6TMLD4X6QX453M",
      "asset": 1002
    }
  ],
  "boxes": [
    {
      "app": 1006,
      "name:b64": "QQ=="
    }
  ],
  "extra-box-refs": 1
}

I don't think there's anything going wrong with the encoding or e2e aspect, but rather a logic error inside the simulate package when multiple different resource types are unspecified. I noticed that the test you're comparing against, TestPopulateResources, is much simpler and only tests accounts. I suggest replicating this in a unit test and debugging further there. Generally speaking the most complex test cases and corner cases should be unit tests in simulation_eval_test.go, then all the e2e test needs to do is make sure all inputs get picked up properly and all outputs are present as expected in the response (to catch encoding errors or fields being dropped).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants