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 35 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 force-pushed the feat/populate_resources branch from 466fd50 to 5ba0a9a Compare June 5, 2024 23:06
@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?

daemon/algod/api/algod.oas2.json Outdated Show resolved Hide resolved
daemon/algod/api/server/v2/handlers.go Outdated Show resolved Hide resolved
daemon/algod/api/server/v2/utils.go Outdated Show resolved Hide resolved
ledger/simulation/resources.go Outdated Show resolved Hide resolved
ledger/simulation/resources.go Outdated Show resolved Hide resolved
}

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".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know what you think of 2ea34f2. Rather than having an additional argument the call sites short circuit if it returns nil

ledger/simulation/resources.go Outdated Show resolved Hide resolved
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

daemon/algod/api/algod.oas2.json Outdated Show resolved Hide resolved
},
"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.

ledger/simulation/resources.go Outdated Show resolved Hide resolved
ledger/simulation/resources.go Outdated Show resolved Hide resolved
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.

daemon/algod/api/server/v2/handlers.go Outdated Show resolved Hide resolved
},
},
}
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).

@joe-p
Copy link
Contributor Author

joe-p commented Oct 9, 2024

In 292a9b9 I updated the API model to avoid using map[int] but for some reason it's not encoding the response properly: msgpack decode error [pos 50]: no matching struct field found when decoding stream map with key PopulatedResourceArrays.

If I print the raw response I see this:

��last-round��txn-groups���PopulatedResourceArrays��app-budget-added����app-budget-consumed��failed-at���failure-message��transaction SSG3ROSUBRSMXPTZYOORYAXCDYURLGM5OJJF5J3LMJ74LFEWJJAA: logic eval error: invalid Account reference CV6S42NRDBJZKQDDQPUVXSD4KUJ5FSYHXENR74ZPTTIEQASD2WBRBFODRY. Details: app=1006, pc=57, opcodes=store 2; load 0; balance�txn-results���app-budget-consumed��txn-result��pool-error��txn��sig�@۔sq��3b�l3���^�J��b�=�29�IұH=� �P��I��r����-e��A���}��q����?<��txn��apid���fee���fv��gh� ��Gp��y8�d��"���\>-c�P�2�P��݆~ݢlv����snd� ��ʏe�Lz�Y��W�[�+!�����O���P�Фtype�appl�version

So for some reason PopulatedResourceArrays is present where I would expect it to be omitted (and if it was included I would expect it to be populated-resource-arrays) given the fact that it's defined as

// PreEncodedSimulateTxnResult mirrors model.SimulateTransactionResult
type PreEncodedSimulateTxnResult struct {
	Txn                      PreEncodedTxInfo                        `codec:"txn-result"`
	AppBudgetConsumed        *uint64                                 `codec:"app-budget-consumed,omitempty"`
	LogicSigBudgetConsumed   *uint64                                 `codec:"logic-sig-budget-consumed,omitempty"`
	TransactionTrace         *model.SimulationTransactionExecTrace   `codec:"exec-trace,omitempty"`
	UnnamedResourcesAccessed *model.SimulateUnnamedResourcesAccessed `codec:"unnamed-resources-accessed,omitempty"`
	FixedSigner              *string                                 `codec:"fixed-signer,omitempty"`
	PopulatedResourceArrays  *model.ResourceArrays                   `codec:"populated-resource-arrays,omitempty"`
}

Any ideas on what might be happening here?

@jannotti
Copy link
Contributor

jannotti commented Oct 9, 2024

It almost seems like the codec line was completely ignored for encoding, since it has the default name and omitempty was ineffective. Yet, decoding was surprised to see the capitalized form. I don't know the context of your testing - is there any chance you encoded that bytestream before the codec line was added, then decoded it after?

@kylebeee
Copy link

kylebeee commented Oct 9, 2024

At a glance it seems to me like you might have a bug somewhere where you're assigning *simulation.PopulatedResourceArrays to PreEncodedSimulateTxnResult.PopulatedResourceArrays instead of *model.ResourceArray but its not super clear where that would be happening & i dont know the go-algorand code base well enough to say definitively.

Where are you printing the raw response?

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.

5 participants