Skip to content

Commit 42fc19d

Browse files
authored
Combine Clear and Approval Program size limits (#2225)
Apps have been able to use 1k space for each of their programs since apps were introduced in v24. But clear state programs are quite small. It is more useful to provide 2k of space, divided however the app prefers. This PR does that, including giving 2k extra space for each unit of "extra pages" requested at app creation time. Tests considering before and after consensus updates.
1 parent cc01fdd commit 42fc19d

File tree

5 files changed

+108
-45
lines changed

5 files changed

+108
-45
lines changed

config/consensus.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,16 @@ type ConsensusParams struct {
232232
// max sum([len(arg) for arg in txn.ApplicationArgs])
233233
MaxAppTotalArgLen int
234234

235-
// maximum length of application approval program or clear state
236-
// program in bytes
235+
// maximum byte len of application approval program or clear state
236+
// When MaxExtraAppProgramPages > 0, this is the size of those pages.
237+
// So two "extra pages" would mean 3*MaxAppProgramLen bytes are available.
237238
MaxAppProgramLen int
238239

240+
// maximum total length of an application's programs (approval + clear state)
241+
// When MaxExtraAppProgramPages > 0, this is the size of those pages.
242+
// So two "extra pages" would mean 3*MaxAppTotalProgramLen bytes are available.
243+
MaxAppTotalProgramLen int
244+
239245
// extra length for application program in pages. A page is MaxAppProgramLen bytes
240246
MaxExtraAppProgramPages int
241247

@@ -832,6 +838,7 @@ func initConsensusProtocols() {
832838
v24.MaxAppArgs = 16
833839
v24.MaxAppTotalArgLen = 2048
834840
v24.MaxAppProgramLen = 1024
841+
v24.MaxAppTotalProgramLen = 2048 // No effect until v28, when MaxAppProgramLen increased
835842
v24.MaxAppKeyLen = 64
836843
v24.MaxAppBytesValueLen = 64
837844

@@ -937,6 +944,7 @@ func initConsensusProtocols() {
937944

938945
// Enable support for larger app program size
939946
vFuture.MaxExtraAppProgramPages = 3
947+
vFuture.MaxAppProgramLen = 2048
940948

941949
// enable the InitialRewardsRateCalculation fix
942950
vFuture.InitialRewardsRateCalculation = true

data/transactions/logic/README_in.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ Starting from version 2 TEAL evaluator can run programs in two modes:
3636
2. Application run (stateful)
3737

3838
Differences between modes include:
39-
1. Max program length (consensus parameters LogicSigMaxSize, MaxApprovalProgramLen and MaxClearStateProgramLen)
39+
1. Max program length (consensus parameters LogicSigMaxSize, MaxAppProgramLen & MaxExtraAppProgramPages)
4040
2. Max program cost (consensus parameters LogicSigMaxCost, MaxAppProgramCost)
41-
3. Opcodes availability. For example, all stateful operations are only available in stateful mode. Refer to [opcodes document](TEAL_opcodes.md) for details.
41+
3. Opcode availability. For example, all stateful operations are only available in stateful mode. Refer to [opcodes document](TEAL_opcodes.md) for details.
4242

4343
## Constants
4444

data/transactions/transaction.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
351351
return fmt.Errorf("local and global state schemas are immutable")
352352
}
353353
if tx.ExtraProgramPages != 0 {
354-
return fmt.Errorf("ExtraProgramPages field is immutable")
354+
return fmt.Errorf("tx.ExtraProgramPages is immutable")
355355
}
356356
}
357357

@@ -389,12 +389,17 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
389389
return fmt.Errorf("tx.ExtraProgramPages too large, max number of extra pages is %d", proto.MaxExtraAppProgramPages)
390390
}
391391

392-
if uint32(len(tx.ApprovalProgram)) > ((1 + tx.ExtraProgramPages) * uint32(proto.MaxAppProgramLen)) {
393-
return fmt.Errorf("approval program too long. max len %d bytes", (1+tx.ExtraProgramPages)*uint32(proto.MaxAppProgramLen))
392+
lap := len(tx.ApprovalProgram)
393+
lcs := len(tx.ClearStateProgram)
394+
pages := int(1 + tx.ExtraProgramPages)
395+
if lap > pages*proto.MaxAppProgramLen {
396+
return fmt.Errorf("approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
394397
}
395-
396-
if uint32(len(tx.ClearStateProgram)) > ((1 + tx.ExtraProgramPages) * uint32(proto.MaxAppProgramLen)) {
397-
return fmt.Errorf("clear state program too long. max len %d bytes", (1+tx.ExtraProgramPages)*uint32(proto.MaxAppProgramLen))
398+
if lcs > pages*proto.MaxAppProgramLen {
399+
return fmt.Errorf("clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
400+
}
401+
if lap+lcs > pages*proto.MaxAppTotalProgramLen {
402+
return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen)
398403
}
399404

400405
if tx.LocalStateSchema.NumEntries() > proto.MaxLocalSchemaEntries {

data/transactions/transaction_test.go

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package transactions
1919
import (
2020
"flag"
2121
"fmt"
22+
"strings"
2223
"testing"
2324

2425
"github.com/algorand/go-algorand/config"
@@ -228,6 +229,12 @@ func TestWellFormedErrors(t *testing.T) {
228229
protoV27 := config.Consensus[protocol.ConsensusV27]
229230
addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
230231
require.NoError(t, err)
232+
okHeader := Header{
233+
Sender: addr1,
234+
Fee: basics.MicroAlgos{Raw: 1000},
235+
LastValid: 105,
236+
FirstValid: 100,
237+
}
231238
usecases := []struct {
232239
tx Transaction
233240
spec SpecialAddresses
@@ -262,15 +269,10 @@ func TestWellFormedErrors(t *testing.T) {
262269
},
263270
{
264271
tx: Transaction{
265-
Type: protocol.ApplicationCallTx,
266-
Header: Header{
267-
Sender: addr1,
268-
Fee: basics.MicroAlgos{Raw: 1000},
269-
LastValid: 105,
270-
FirstValid: 100,
271-
},
272+
Type: protocol.ApplicationCallTx,
273+
Header: okHeader,
272274
ApplicationCallTxnFields: ApplicationCallTxnFields{
273-
ApplicationID: 0,
275+
ApplicationID: 0, // creation
274276
ApplicationArgs: [][]byte{
275277
[]byte("write"),
276278
},
@@ -279,17 +281,67 @@ func TestWellFormedErrors(t *testing.T) {
279281
},
280282
spec: specialAddr,
281283
proto: protoV27,
282-
expectedError: fmt.Errorf("tx.ExtraProgramPages too large, max number of extra pages is %d", curProto.MaxExtraAppProgramPages),
284+
expectedError: fmt.Errorf("tx.ExtraProgramPages too large, max number of extra pages is %d", protoV27.MaxExtraAppProgramPages),
283285
},
284286
{
285287
tx: Transaction{
286-
Type: protocol.ApplicationCallTx,
287-
Header: Header{
288-
Sender: addr1,
289-
Fee: basics.MicroAlgos{Raw: 1000},
290-
LastValid: 105,
291-
FirstValid: 100,
288+
Type: protocol.ApplicationCallTx,
289+
Header: okHeader,
290+
ApplicationCallTxnFields: ApplicationCallTxnFields{
291+
ApplicationID: 0, // creation
292+
ApprovalProgram: []byte(strings.Repeat("X", 1025)),
293+
ClearStateProgram: []byte("junk"),
294+
},
295+
},
296+
spec: specialAddr,
297+
proto: protoV27,
298+
expectedError: fmt.Errorf("approval program too long. max len 1024 bytes"),
299+
},
300+
{
301+
tx: Transaction{
302+
Type: protocol.ApplicationCallTx,
303+
Header: okHeader,
304+
ApplicationCallTxnFields: ApplicationCallTxnFields{
305+
ApplicationID: 0, // creation
306+
ApprovalProgram: []byte(strings.Repeat("X", 1025)),
307+
ClearStateProgram: []byte("junk"),
308+
},
309+
},
310+
spec: specialAddr,
311+
proto: futureProto,
312+
},
313+
{
314+
tx: Transaction{
315+
Type: protocol.ApplicationCallTx,
316+
Header: okHeader,
317+
ApplicationCallTxnFields: ApplicationCallTxnFields{
318+
ApplicationID: 0, // creation
319+
ApprovalProgram: []byte(strings.Repeat("X", 1025)),
320+
ClearStateProgram: []byte(strings.Repeat("X", 1025)),
292321
},
322+
},
323+
spec: specialAddr,
324+
proto: futureProto,
325+
expectedError: fmt.Errorf("app programs too long. max total len 2048 bytes"),
326+
},
327+
{
328+
tx: Transaction{
329+
Type: protocol.ApplicationCallTx,
330+
Header: okHeader,
331+
ApplicationCallTxnFields: ApplicationCallTxnFields{
332+
ApplicationID: 0, // creation
333+
ApprovalProgram: []byte(strings.Repeat("X", 1025)),
334+
ClearStateProgram: []byte(strings.Repeat("X", 1025)),
335+
ExtraProgramPages: 1,
336+
},
337+
},
338+
spec: specialAddr,
339+
proto: futureProto,
340+
},
341+
{
342+
tx: Transaction{
343+
Type: protocol.ApplicationCallTx,
344+
Header: okHeader,
293345
ApplicationCallTxnFields: ApplicationCallTxnFields{
294346
ApplicationID: 1,
295347
ApplicationArgs: [][]byte{
@@ -300,17 +352,12 @@ func TestWellFormedErrors(t *testing.T) {
300352
},
301353
spec: specialAddr,
302354
proto: futureProto,
303-
expectedError: fmt.Errorf("ExtraProgramPages field is immutable"),
355+
expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"),
304356
},
305357
{
306358
tx: Transaction{
307-
Type: protocol.ApplicationCallTx,
308-
Header: Header{
309-
Sender: addr1,
310-
Fee: basics.MicroAlgos{Raw: 1000},
311-
LastValid: 105,
312-
FirstValid: 100,
313-
},
359+
Type: protocol.ApplicationCallTx,
360+
Header: okHeader,
314361
ApplicationCallTxnFields: ApplicationCallTxnFields{
315362
ApplicationID: 0,
316363
ApplicationArgs: [][]byte{

test/scripts/e2e_client_runner.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
# Usage:
1414
# ./e2e_client_runner.py e2e_subs/*.sh
1515
#
16-
# Reads each bash script for `# TIMEOUT=N` line to configure timeout to N seconds. (default timeout is 200 seconds)
16+
# Reads each bash script for `# TIMEOUT=N` line to configure timeout
17+
# to N seconds. The default is 10 seconds less than the timeout
18+
# associated with the entire set of tests, which defaults to 500, but
19+
# can be controlled with --timeout
1720

1821
import argparse
1922
import atexit
@@ -67,7 +70,7 @@ def read_script_for_timeout(fname):
6770
return int(m.group(1))
6871
except:
6972
logger.debug('read timeout match err', exc_info=True)
70-
return 200
73+
return None
7174

7275

7376
def create_kmd_config_with_unsafe_scrypt(working_dir):
@@ -94,9 +97,8 @@ def create_kmd_config_with_unsafe_scrypt(working_dir):
9497
json.dump(kmd_conf_data,f)
9598

9699

97-
98100

99-
def _script_thread_inner(runset, scriptname):
101+
def _script_thread_inner(runset, scriptname, timeout):
100102
start = time.time()
101103
algod, kmd = runset.connect()
102104
pubw, maxpubaddr = runset.get_pub_wallet()
@@ -138,7 +140,9 @@ def _script_thread_inner(runset, scriptname):
138140
p = subprocess.Popen([scriptname, walletname], env=env, cwd=repodir, stdout=cmdlog, stderr=subprocess.STDOUT)
139141
cmdlog.close()
140142
runset.running(scriptname, p)
141-
timeout = read_script_for_timeout(scriptname)
143+
script_timeout = read_script_for_timeout(scriptname)
144+
if script_timeout:
145+
timeout = script_timeout
142146
try:
143147
retcode = p.wait(timeout)
144148
except subprocess.TimeoutExpired as te:
@@ -175,10 +179,10 @@ def _script_thread_inner(runset, scriptname):
175179
runset.done(scriptname, retcode == 0, dt)
176180
return
177181

178-
def script_thread(runset, scriptname):
182+
def script_thread(runset, scriptname, to):
179183
start = time.time()
180184
try:
181-
_script_thread_inner(runset, scriptname)
185+
_script_thread_inner(runset, scriptname, to)
182186
except Exception as e:
183187
logger.error('error in e2e_client_runner.py', exc_info=True)
184188
runset.done(scriptname, False, time.time() - start)
@@ -218,10 +222,9 @@ def _connect(self):
218222
if self.algod and self.kmd:
219223
return
220224

221-
222225
# should run from inside self.lock
223226
algodata = self.env['ALGORAND_DATA']
224-
227+
225228
xrun(['goal', 'kmd', 'start', '-t', '3600','-d', algodata], env=self.env, timeout=5)
226229
self.kmd = openkmd(algodata)
227230
self.algod = openalgod(algodata)
@@ -250,11 +253,11 @@ def get_pub_wallet(self):
250253
self.maxpubaddr = maxpubaddr
251254
return self.pubw, self.maxpubaddr
252255

253-
def start(self, scriptname):
256+
def start(self, scriptname, timeout):
254257
with self.lock:
255258
if not self.ok:
256259
return
257-
t = threading.Thread(target=script_thread, args=(self, scriptname,))
260+
t = threading.Thread(target=script_thread, args=(self, scriptname, timeout))
258261
t.start()
259262
with self.lock:
260263
self.threads[scriptname] = t
@@ -450,7 +453,7 @@ def main():
450453

451454
rs = RunSet(env)
452455
for scriptname in args.scripts:
453-
rs.start(scriptname)
456+
rs.start(scriptname, args.timeout-10)
454457
rs.wait(args.timeout)
455458
if rs.errors:
456459
retcode = 1

0 commit comments

Comments
 (0)