diff --git a/config/bounds/bounds.go b/config/bounds/bounds.go index 6a3a302c12..db451ecec4 100644 --- a/config/bounds/bounds.go +++ b/config/bounds/bounds.go @@ -115,6 +115,9 @@ var MaxTxnBytesPerBlock int // MaxAppTxnForeignApps is the max number of foreign apps per txn across all consensus versions var MaxAppTxnForeignApps int +// MaxAppAccess is the max number of references allowed in txn.Access per txn across all consensus versions +var MaxAppAccess int + // MaxEvalDeltaTotalLogSize is the maximum size of the sum of all log sizes in a single eval delta. const MaxEvalDeltaTotalLogSize = 1024 diff --git a/config/consensus.go b/config/consensus.go index 1365686b79..a464c250dc 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -765,6 +765,7 @@ func checkSetAllocBounds(p ConsensusParams) { checkSetMax(p.MaxTxnBytesPerBlock, &bounds.MaxTxnBytesPerBlock) checkSetMax(p.MaxAppTxnForeignApps, &bounds.MaxAppTxnForeignApps) + checkSetMax(p.MaxAppAccess, &bounds.MaxAppAccess) } // DeepCopy creates a deep copy of a consensus protocols map. diff --git a/data/appRateLimiter.go b/data/appRateLimiter.go index 5155335ba5..0fd5a27121 100644 --- a/data/appRateLimiter.go +++ b/data/appRateLimiter.go @@ -265,7 +265,7 @@ func txgroupToKeys(txgroup []transactions.SignedTxn, origin []byte, seed uint64, txnToBucket := func(appIdx basics.AppIndex) int { return int(memhash64(uint64(appIdx), seed) % uint64(numBuckets)) } - seen := make(map[basics.AppIndex]struct{}, len(txgroup)*(1+bounds.MaxAppTxnForeignApps)) + seen := make(map[basics.AppIndex]struct{}, len(txgroup)*(1+max(bounds.MaxAppTxnForeignApps, bounds.MaxAppAccess))) valid := func(appIdx basics.AppIndex) bool { if appIdx != 0 { _, ok := seen[appIdx] @@ -273,24 +273,24 @@ func txgroupToKeys(txgroup []transactions.SignedTxn, origin []byte, seed uint64, } return false } + record := func(appIdx basics.AppIndex) { + // hash appIdx into a bucket, do not use modulo without hashing first since it could + // assign two vanilla (and presumable, popular) apps to the same bucket. + if valid(appIdx) { + keysBuckets.buckets = append(keysBuckets.buckets, txnToBucket(appIdx)) + keysBuckets.keys = append(keysBuckets.keys, txnToDigest(appIdx)) + seen[appIdx] = struct{}{} + } + } for i := range txgroup { if txgroup[i].Txn.Type == protocol.ApplicationCallTx { appIdx := txgroup[i].Txn.ApplicationID - if valid(appIdx) { - keysBuckets.buckets = append(keysBuckets.buckets, txnToBucket(appIdx)) - keysBuckets.keys = append(keysBuckets.keys, txnToDigest(appIdx)) - seen[appIdx] = struct{}{} + record(appIdx) + for _, appIdx := range txgroup[i].Txn.ForeignApps { + record(appIdx) } - // hash appIdx into a bucket, do not use modulo without hashing first since it could - // assign two vanilla (and presumable, popular) apps to the same bucket. - if len(txgroup[i].Txn.ForeignApps) > 0 { - for _, appIdx := range txgroup[i].Txn.ForeignApps { - if valid(appIdx) { - keysBuckets.buckets = append(keysBuckets.buckets, txnToBucket(appIdx)) - keysBuckets.keys = append(keysBuckets.keys, txnToDigest(appIdx)) - seen[appIdx] = struct{}{} - } - } + for _, acc := range txgroup[i].Txn.Access { + record(acc.App) } } } diff --git a/data/appRateLimiter_test.go b/data/appRateLimiter_test.go index 9e58154fb6..102a2b5c3a 100644 --- a/data/appRateLimiter_test.go +++ b/data/appRateLimiter_test.go @@ -459,32 +459,48 @@ func TestAppRateLimiter_TxgroupToKeys(t *testing.T) { kb := txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) require.Equal(t, 0, len(kb.keys)) - require.Equal(t, len(kb.buckets), len(kb.buckets)) + require.Equal(t, len(kb.keys), len(kb.buckets)) putAppKeyBuf(kb) txgroup[0].Txn.ApplicationID = 1 kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) require.Equal(t, 1, len(kb.keys)) - require.Equal(t, len(kb.buckets), len(kb.buckets)) + require.Equal(t, len(kb.keys), len(kb.buckets)) putAppKeyBuf(kb) txgroup[0].Txn.ForeignApps = append(txgroup[0].Txn.ForeignApps, 1) kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) require.Equal(t, 1, len(kb.keys)) - require.Equal(t, len(kb.buckets), len(kb.buckets)) + require.Equal(t, len(kb.keys), len(kb.buckets)) putAppKeyBuf(kb) txgroup[0].Txn.ForeignApps = append(txgroup[0].Txn.ForeignApps, 2) kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) require.Equal(t, 2, len(kb.keys)) - require.Equal(t, len(kb.buckets), len(kb.buckets)) + require.Equal(t, len(kb.keys), len(kb.buckets)) putAppKeyBuf(kb) apptxn.ApplicationID = 2 txgroup = append(txgroup, transactions.SignedTxn{Txn: apptxn}) kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) require.Equal(t, 2, len(kb.keys)) - require.Equal(t, len(kb.buckets), len(kb.buckets)) + require.Equal(t, len(kb.keys), len(kb.buckets)) + putAppKeyBuf(kb) + + // new app if from access list + apptxn.Access = []transactions.ResourceRef{{App: 3}} + txgroup = append(txgroup, transactions.SignedTxn{Txn: apptxn}) + kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 3, len(kb.keys)) + require.Equal(t, len(kb.keys), len(kb.buckets)) + putAppKeyBuf(kb) + + // known app id in access list + apptxn.Access = []transactions.ResourceRef{{App: 3}, {App: 2}} + txgroup = append(txgroup, transactions.SignedTxn{Txn: apptxn}) + kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 3, len(kb.keys)) + require.Equal(t, len(kb.keys), len(kb.buckets)) putAppKeyBuf(kb) }