Skip to content

Apps: txn.Access list for access to more resources #6286

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

Open
wants to merge 35 commits into
base: master
Choose a base branch
from

Conversation

jannotti
Copy link
Contributor

@jannotti jannotti commented Mar 26, 2025

Summary

Today, app developers find it frustrating that they can only list 8 resources in a transaction. This number is artificially low because there are rules that allow access to many more than 8 items when, for example, 4 account and 4 apps are listed.

This PR introduced a single unified Access field on app calls, which contains all accounts, apps, asas, and boxes that the transaction can touch. Because no extra rules allow access to extra resources, we can expand the allowable size of such a list. 32 seems likely. (edit: currently thinking 16, but also upping the box quota per reference to 2k)

This will probably increase performance, since it will now be reasonable to perform perfect prefetching of all resources an app call might touch.

This PR also augments goal to create these transactions if the --access flag is used. It still needs e2e_subs tests that exercise it. changing back to draft until I write those

This PR does not actually implement the improved pre-fetching. Should it?

Test Plan

@joe-p
Copy link
Contributor

joe-p commented Mar 26, 2025

This shouldn't need a new AVM version, correct? Since AVM9 we've had group resource sharing, which means an app has no way of knowing what resources are available. Although I suppose it could check if it's an outer call and check the rest of the group, but seems highly improbable to account for all the sharing rules even if an app wanted to do that.

@jannotti
Copy link
Contributor Author

jannotti commented Mar 26, 2025

I think you're asking "will only new programs, AVM v12, be able to use this?"

I think it should be ok to let old programs use this, including letting these resources be accessed by other programs in the same group that are a low version. I'm pretty sure we made the same decision with resource pooling. After the consensus upgrade, programs suddenly got access to things they didn't "know" they had access to by looking into the arrays.

I'll have to confirm whether we put in any limitations. For example, I think it would be unsafe to let v3 (!) programs see extra ASAs.

Copy link

codecov bot commented Mar 26, 2025

Codecov Report

Attention: Patch coverage is 61.25000% with 310 lines in your changes missing coverage. Please review.

Project coverage is 50.72%. Comparing base (9adaf55) to head (82b6abb).
Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
cmd/goal/application.go 29.19% 113 Missing and 1 partial ⚠️
data/basics/testing/nearzero.go 69.29% 35 Missing and 4 partials ⚠️
data/transactions/application.go 81.43% 26 Missing and 5 partials ⚠️
libgoal/transactions.go 80.83% 23 Missing ⚠️
util/fn.go 0.00% 23 Missing ⚠️
shared/pingpong/pingpong.go 0.00% 18 Missing ⚠️
cmd/goal/interact.go 0.00% 14 Missing ⚠️
cmd/goal/asset.go 0.00% 12 Missing ⚠️
daemon/algod/api/server/v2/utils.go 0.00% 12 Missing ⚠️
cmd/goal/clerk.go 0.00% 6 Missing ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6286      +/-   ##
==========================================
+ Coverage   49.81%   50.72%   +0.90%     
==========================================
  Files         356      655     +299     
  Lines       64417   111179   +46762     
==========================================
+ Hits        32091    56392   +24301     
- Misses      30970    51921   +20951     
- Partials     1356     2866    +1510     

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

@joe-p
Copy link
Contributor

joe-p commented Mar 27, 2025

Right that's what I would think as well. The main reason I ask is because if we don't require a specific AVM version existing applications, even if immutable, can leverage this feature by simply updating their client-side code.

@jannotti
Copy link
Contributor Author

jannotti commented Apr 2, 2025

Right that's what I would think as well. The main reason I ask is because if we don't require a specific AVM version existing applications, even if immutable, can leverage this feature by simply updating their client-side code.

Having worked on it some more:

Yes, any transaction can use tx.Access instead of the existing arrays, even if the transaction is an app call to an old app. Old apps that use "slots" with opcodes will index into tx.Access instead of the respective array. That element of tx.Access has to be the proper type. So int 3; int 4; asset_holding_get will expect an account in tx.Access[3-1] and an asset in tx.Access[4-1]. (The -1 is because tx.Access is always 1-based so that 0 can mean Sender or current app for address and apps.)

As for sharing, we only enabled sharing for v9 programs and higher, so I made that true here as well. Your v9 or higher programs will be able to see the resources made available from other transactions, whether those transactions made the resources available with the "old-style" foreign arrays or with tx.Access.

Like tx.Boxes, I do not plan on making tx.Access explicitly visible to apps, there will not be txna Access 3. And inner transactions will not be able to populate tx.Access for their inners. The first limitation is to prevent apps from the bad practice of passing arguments in tx.Access. That should be done explicitly, so that availability can be handled in various ways (maybe another transaction has the resource in tx.Access, for example). The second limitation is to reduce complexity - generally you shouldn't need to pass tx.Access because of resource sharing.

@jannotti jannotti force-pushed the access-list branch 5 times, most recently from 98e1981 to 1573e6b Compare April 2, 2025 18:56
@jannotti jannotti marked this pull request as ready for review April 4, 2025 15:39
Copy link
Contributor

@algorandskiy algorandskiy left a comment

Choose a reason for hiding this comment

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

  1. Left some comments
  2. Empty box ref in Access gaining extra reads budged should be somehow better documented

@cce cce requested a review from Copilot July 2, 2025 14:37
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a unified Access list for application calls, replacing the old separate foreign-arrays and allowing more than the previous 8 total references. It updates the Go SDK (libgoal) to accept a new RefBundle type in all app-transaction constructors, implements logic to translate that bundle into either legacy foreign arrays or the new access list, and adjusts TEAL programs and integration tests to exercise and validate the new behavior.

  • Introduced RefBundle and updated MakeUnsignedApp*Tx methods in libgoal/transactions.go
  • Added attachAccessList/attachForeignRefs routines to populate transactions from RefBundle
  • Bumped TEAL versions and updated e2e scripts/tests to use --access and expect new “unavailable” errors

Reviewed Changes

Copilot reviewed 52 out of 53 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
util/fn.go add generic Map/MapErr helpers
test/scripts/e2e_subs/tealprogs/xappreads.teal bump to TEAL v9, replace failure labels with assert
test/scripts/e2e_subs/tealprogs/assets-escrow9.teal new TEAL program demonstrating tx.Access usage
test/scripts/e2e_subs/shared-resources.py update error substrings to “unavailable”
test/scripts/e2e_subs/e2e-app-x-app-reads.sh revise CLI tests to use --access and expect new errors
test/scripts/e2e_subs/e2e-app-simulate.sh update expected failure message to “unavailable Account”
test/scripts/e2e_subs/app-assets.sh add exit-on-failure logic and consistent quoting
test/scripts/e2e_subs/app-assets-access.sh new end-to-end asset tests with pervasive --access
test/e2e-go/upgrades/application_support_test.go update calls to use libgoal.RefBundle{}
test/e2e-go/restAPI/simulate/simulateRestAPI_test.go adjust simulation REST tests for new RefBundle signature
test/e2e-go/restAPI/other/appsRestAPI_test.go adapt REST API app-creation tests to new RefBundle
test/e2e-go/features/transactions/application_test.go refactor feature tests to use RefBundle in app calls
test/e2e-go/features/transactions/app_pages_test.go update extra-pages app-create/update tests for RefBundle
test/e2e-go/features/transactions/accountv2_test.go adjust account-v2 tests to new app-call signature
test/e2e-go/features/accountPerf/sixMillion_test.go update performance tests for app-call ref changes
shared/pingpong/pingpong.go switch pingpong to RefBundle and use slices
shared/pingpong/accounts.go update pingpong account flows to new app-call API
scripts/export_sdk_types.py extend export script to include new resource types
libgoal/transactions.go introduce RefBundle, attachReferences, new access/foreign logic
libgoal/libgoal_test.go add unit tests for attachForeignRefs and attachAccessList
ledger/simulation/simulation_eval_test.go update simulation tests to use basics.BoxRef
ledger/simulation/resources.go switch ResourceTracker.Boxes to map[basics.BoxRef]
ledger/boxtxn_test.go bump box quota version and adjust budget checks
ledger/apptxn_test.go typo fix in comment and update expected “unavailable” message
ledger/apply/application_test.go remove obsolete tests, add t.Parallel()
ledger/apply/application.go adjust checkPrograms to new gi parameter
data/txntest/txn.go add Access []ResourceRef to Txn stub
data/transactions/msgp_gen_test.go add msgpack tests for HoldingRef, LocalsRef, ResourceRef
data/transactions/logic/resources_test.go update expected “unavailable Local State” formatting
data/transactions/logic/resources.go use basics.BoxRef and implement fillApplicationCallAccess
data/transactions/logic/export_test.go export new helpers for full-app tests
data/transactions/logic/eval_test.go replace convertSlice with util.Map and import util

// that can be attached to a single app call.
// maximum number of "foreign references" (accounts, asa, app, boxes) that
// can be attached to a single app call. Modern transactions can use
// MaxAccess references in txn.Access to access more.
Copy link
Contributor

Choose a reason for hiding this comment

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

is this supposed to refer to MaxAppAccess?

// w/ AssetSender) even if the Sender holding of the asset is not
// available. This parameters can be removed and assumed true after the
// first consensus release in which it is set true.
EnableInnerClawbackWithoutSenderHolding bool
Copy link
Contributor

Choose a reason for hiding this comment

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

IDEA: These parameters that are 'future-removable' - perhaps we should prefix them with 'Temp' or similar?

@@ -1425,6 +1435,12 @@ func initConsensusProtocols() {
vFuture.EnableAppVersioning = true // if not promoted when v12 goes into effect, update logic/field.go
vFuture.EnableSha512BlockHash = true

// txn.Access work
vFuture.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same
Copy link
Contributor

Choose a reason for hiding this comment

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

Referring to the v24 comment, the idea is that we've capped with MaxAppAccess below, allowing us to loosen up here. Why not put all of them at 16?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This 8 is only to bring Accounts inline with ForeignApps and ForiegnAssets, which are also 8. We need to keep these low, because of their cross-product implications. But Accounts was artificially low because a was dumb, years ago. It is nicer to have them all the same (but still lower than allowed in the access list).

Think of this as unrelated to tx.Access. (but I'm cramming it into this PR because it's tiny, and in the same vicinity)

return 0, string(br.Name), nil
case br.Index <= uint64(len(access)): // 1-based
app = access[br.Index-1].App
if app == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

because this implies not existing?

Copy link
Contributor Author

@jannotti jannotti Jul 10, 2025

Choose a reason for hiding this comment

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

The box claims that it's for the app in "slot" br.Index of the access list. But that ResourceRef has nothing in .App , so that ResourceRef is not, in fact, an app. Maybe it's an Account or Assets, etc.

I have an idea to make this a little clearer.

658ac8f

@@ -5211,7 +5219,10 @@ func (cx *EvalContext) assignAsset(sv stackValue) (basics.AssetIndex, error) {
// transaction (axfer,acfg,afrz), but not for holding lookups or assignments to
// an inner static array.
func (cx *EvalContext) availableAsset(aid basics.AssetIndex) bool {
// Ensure that aid is in Foreign Assets
// Check if aid is in an access array
Copy link
Contributor

Choose a reason for hiding this comment

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

Access does not have the 0 special case like the other slices, so this should all work.

input = input.replace("Boxes []BoxRef", "BoxReferences []BoxReference")
input = re.sub("Box\\s+BoxRef", "Box BoxReference", input)
Copy link
Contributor

Choose a reason for hiding this comment

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

how did things look running this locally?

@@ -1154,7 +1154,7 @@ int 1

// create the app
appTx, err = client.MakeUnsignedAppCreateTx(
transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0)
transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, libgoal.RefBundle{}, 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Works as long as we run against vfuture, would fail on current right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this would work on both, since it uses no references. RefBundle is just a way to group those resources into one argument. But here it's empty (and the args were nil)

@@ -128,6 +128,7 @@ var passThruSource = main(`
`)

const boxVersion = 36
const boxQuotaBumpVersion = 41
Copy link
Contributor

Choose a reason for hiding this comment

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

Why 41 rather than 40?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're currently at v40, vFuture == 41. (And when vFuture is bumped, we should be releasing v41 which will have the quota bump)

// update, we should remove it from consensus params and assume it's true in
// the next release. It only needs to be in there so that it gates the
// beahvior change in the release it first appears.
if !cx.Proto.EnableInnerClawbackWithoutSenderHolding || tx.AssetSender.IsZero() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comment elsewhere; these 'use once and move on' params could maybe go by a special prefix or suffix..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I like the idea.

Will it cause any problems in Go SDK when we remove the flag? We export the consensusparams struct to the Go SDK. Does Go json reading fail when there's a mismatch of json to the struct? Or just leave fields zero?

@gmalouf
Copy link
Contributor

gmalouf commented Jul 10, 2025 via email

jannotti and others added 4 commits July 10, 2025 14:33
And make the entire decription clearer, especially for
holdings/locals.
later application calls in the group, whether those application
calls are top-level or inner.

* v9 and later applications may use the `txn.Access` list instead of
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the top level transaction alone determine the availability in all inners? i.e. what if an inner involves a pre-v9 application, etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's the version that's running that matters. So if a v5 calls a v9, the v9 will have access to group shared resources.

If v9 calls v7, it must pass the resources down in foreign-array fields when it creates the inner transaction, because it will only have access to locally available stuff.

func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) {
func getAppInputsFromFile() appCallInputs {
reportWarnf("Using a JSON app input file is deprecated and will be removed soon. Please speak up if the feature matters to you.")
time.Sleep(5 * time.Second)
Copy link
Contributor

Choose a reason for hiding this comment

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

that's one way to encourage folks to change!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's slightly friendlier than my preferred way: delete the code.

Copy link
Contributor

@gmalouf gmalouf left a comment

Choose a reason for hiding this comment

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

I left comments/questions where I had them; overall I think this makes it a lot easier for developers and is a nice usability improvement!

Copy link
Contributor

@yossigi yossigi left a comment

Choose a reason for hiding this comment

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

Looks good to me. I only had a couple of small comments.

@@ -54,6 +55,12 @@ const (
// can contain. Its value is verified against consensus parameters in
// TestEncodedAppTxnAllocationBounds
encodedMaxBoxes = 32

// encodedMaxAcces sets the allocation bound for the maximum number of
Copy link
Contributor

Choose a reason for hiding this comment

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

comment nit: encodedMaxAcces -> encodedMaxAccess

contract present in the `txn.ForeignApplications` field is
_available_.

* in v4 and above applications, Holdings and Locals are _available_
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: it's hard to follow. Sorting by version number would help a bit.

// that can be attached to a single app call.
// maximum number of "foreign references" (accounts, asa, app, boxes) that
// can be attached to a single app call. Modern transactions can use
// MaxAccess references in txn.Access to access more.
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
// MaxAccess references in txn.Access to access more.
// MaxAppAccess references in txn.Access to access more.

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.

6 participants