diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go
index a69ed5be98..42907927d1 100644
--- a/cmd/goal/clerk.go
+++ b/cmd/goal/clerk.go
@@ -980,16 +980,6 @@ func assembleFileImpl(fname string, printWarnings bool) *logic.OpStream {
ops.ReportMultipleErrors(fname, os.Stderr)
reportErrorf("%s: %s", fname, err)
}
- _, params := getProto(protoVersion)
- if ops.HasStatefulOps {
- if len(ops.Program) > config.MaxAvailableAppProgramLen {
- reportErrorf(tealAppSize, fname, len(ops.Program), config.MaxAvailableAppProgramLen)
- }
- } else {
- if uint64(len(ops.Program)) > params.LogicSigMaxSize {
- reportErrorf(tealLogicSigSize, fname, len(ops.Program), params.LogicSigMaxSize)
- }
- }
if printWarnings && len(ops.Warnings) != 0 {
for _, warning := range ops.Warnings {
@@ -1179,14 +1169,19 @@ var dryrunCmd = &cobra.Command{
if timeStamp <= 0 {
timeStamp = time.Now().Unix()
}
+
+ lSigPooledSize := 0
for i, txn := range stxns {
if txn.Lsig.Blank() {
continue
}
- if uint64(txn.Lsig.Len()) > params.LogicSigMaxSize {
+ lsigLen := txn.Lsig.Len()
+ lSigPooledSize += lsigLen
+ if !params.EnableLogicSigSizePooling && uint64(lsigLen) > params.LogicSigMaxSize {
reportErrorf("program size too large: %d > %d", len(txn.Lsig.Logic), params.LogicSigMaxSize)
}
ep := logic.NewSigEvalParams(stxns, ¶ms, logic.NoHeaderLedger{})
+
err := logic.CheckSignature(i, ep)
if err != nil {
reportErrorf("program failed Check: %s", err)
@@ -1204,6 +1199,10 @@ var dryrunCmd = &cobra.Command{
fmt.Fprintf(os.Stdout, "ERROR: %s\n", err.Error())
}
}
+ lSigMaxPooledSize := len(stxns) * int(params.LogicSigMaxSize)
+ if params.EnableLogicSigSizePooling && lSigPooledSize > lSigMaxPooledSize {
+ reportErrorf("total lsigs size too large: %d > %d", lSigPooledSize, lSigMaxPooledSize)
+ }
},
}
diff --git a/config/consensus.go b/config/consensus.go
index c5f3801b8e..7e111ecc89 100644
--- a/config/consensus.go
+++ b/config/consensus.go
@@ -113,9 +113,13 @@ type ConsensusParams struct {
EnableAppCostPooling bool
// EnableLogicSigCostPooling specifies LogicSig budgets are pooled across a
- // group. The total available is len(group) * LogicSigMaxCost)
+ // group. The total available is len(group) * LogicSigMaxCost
EnableLogicSigCostPooling bool
+ // EnableLogicSigSizePooling specifies LogicSig sizes are pooled across a
+ // group. The total available is len(group) * LogicSigMaxSize
+ EnableLogicSigSizePooling bool
+
// RewardUnit specifies the number of MicroAlgos corresponding to one reward
// unit.
//
@@ -228,7 +232,7 @@ type ConsensusParams struct {
// 0 for no support, otherwise highest version supported
LogicSigVersion uint64
- // len(LogicSig.Logic) + len(LogicSig.Args[*]) must be less than this
+ // len(LogicSig.Logic) + len(LogicSig.Args[*]) must be less than this (unless pooling is enabled)
LogicSigMaxSize uint64
// sum of estimated op cost must be less than this
@@ -765,7 +769,7 @@ func checkSetAllocBounds(p ConsensusParams) {
checkSetMax(p.MaxAppProgramLen, &MaxStateDeltaKeys)
checkSetMax(p.MaxAppProgramLen, &MaxEvalDeltaAccounts)
checkSetMax(p.MaxAppProgramLen, &MaxAppProgramLen)
- checkSetMax(int(p.LogicSigMaxSize), &MaxLogicSigMaxSize)
+ checkSetMax((int(p.LogicSigMaxSize) * p.MaxTxGroupSize), &MaxLogicSigMaxSize)
checkSetMax(p.MaxTxnNoteBytes, &MaxTxnNoteBytes)
checkSetMax(p.MaxTxGroupSize, &MaxTxGroupSize)
// MaxBytesKeyValueLen is max of MaxAppKeyLen and MaxAppBytesValueLen
@@ -1512,6 +1516,8 @@ func initConsensusProtocols() {
vFuture.LogicSigVersion = 11 // When moving this to a release, put a new higher LogicSigVersion here
+ vFuture.EnableLogicSigSizePooling = true
+
vFuture.Payouts.Enabled = true
vFuture.Payouts.Percent = 75
vFuture.Payouts.GoOnlineFee = 2_000_000 // 2 algos
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index 4da436a1b6..cc4fccc2a7 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -1000,6 +1000,7 @@ func (pe panicError) Error() string {
var errLogicSigNotSupported = errors.New("LogicSig not supported")
var errTooManyArgs = errors.New("LogicSig has too many arguments")
+var errLogicSigArgTooLarge = errors.New("LogicSig argument too large")
// EvalError indicates AVM evaluation failure
type EvalError struct {
@@ -1305,8 +1306,15 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
if (cx.EvalParams.Proto == nil) || (cx.EvalParams.Proto.LogicSigVersion == 0) {
return false, errLogicSigNotSupported
}
- if cx.txn.Lsig.Args != nil && len(cx.txn.Lsig.Args) > transactions.EvalMaxArgs {
- return false, errTooManyArgs
+ if cx.txn.Lsig.Args != nil {
+ if len(cx.txn.Lsig.Args) > transactions.EvalMaxArgs {
+ return false, errTooManyArgs
+ }
+ for _, arg := range cx.txn.Lsig.Args {
+ if len(arg) > transactions.MaxLogicSigArgSize {
+ return false, errLogicSigArgTooLarge
+ }
+ }
}
if verr != nil {
return false, verr
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index c8f7a8bc5f..8f93fa564b 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -258,6 +258,24 @@ func TestTooManyArgs(t *testing.T) {
}
}
+func TestArgTooLarge(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ t.Parallel()
+ for v := uint64(1); v <= AssemblerMaxVersion; v++ {
+ t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
+ ops := testProg(t, "int 1", v)
+ var txn transactions.SignedTxn
+ txn.Lsig.Logic = ops.Program
+ txn.Lsig.Args = [][]byte{make([]byte, transactions.MaxLogicSigArgSize+1)}
+ pass, err := EvalSignature(0, defaultSigParams(txn))
+ require.Error(t, err)
+ require.False(t, pass)
+ })
+ }
+
+}
+
func TestEmptyProgram(t *testing.T) {
partitiontest.PartitionTest(t)
diff --git a/data/transactions/logicsig.go b/data/transactions/logicsig.go
index 31a14ea1dc..15a828ca60 100644
--- a/data/transactions/logicsig.go
+++ b/data/transactions/logicsig.go
@@ -25,6 +25,11 @@ import (
// EvalMaxArgs is the maximum number of arguments to an LSig
const EvalMaxArgs = 255
+// MaxLogicSigArgSize is the maximum size of an argument to an LSig
+// We use 4096 to match the maximum size of a TEAL value
+// (as defined in `const maxStringSize` in package logic)
+const MaxLogicSigArgSize = 4096
+
// LogicSig contains logic for validating a transaction.
// LogicSig is signed by an account, allowing delegation of operations.
// OR
@@ -39,7 +44,7 @@ type LogicSig struct {
Msig crypto.MultisigSig `codec:"msig"`
// Args are not signed, but checked by Logic
- Args [][]byte `codec:"arg,allocbound=EvalMaxArgs,allocbound=config.MaxLogicSigMaxSize"`
+ Args [][]byte `codec:"arg,allocbound=EvalMaxArgs,allocbound=MaxLogicSigArgSize,maxtotalbytes=config.MaxLogicSigMaxSize"`
}
// Blank returns true if there is no content in this LogicSig
diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go
index 7cc22db08a..15cd34ef2d 100644
--- a/data/transactions/msgp_gen.go
+++ b/data/transactions/msgp_gen.go
@@ -3306,8 +3306,8 @@ func (z *LogicSig) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o
err = msgp.WrapError(err, "struct-from-array", "Args", zb0001)
return
}
- if zb0007 > config.MaxLogicSigMaxSize {
- err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxLogicSigMaxSize))
+ if zb0007 > MaxLogicSigArgSize {
+ err = msgp.ErrOverflow(uint64(zb0007), uint64(MaxLogicSigArgSize))
return
}
(*z).Args[zb0001], bts, err = msgp.ReadBytesBytes(bts, (*z).Args[zb0001])
@@ -3395,8 +3395,8 @@ func (z *LogicSig) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o
err = msgp.WrapError(err, "Args", zb0001)
return
}
- if zb0011 > config.MaxLogicSigMaxSize {
- err = msgp.ErrOverflow(uint64(zb0011), uint64(config.MaxLogicSigMaxSize))
+ if zb0011 > MaxLogicSigArgSize {
+ err = msgp.ErrOverflow(uint64(zb0011), uint64(MaxLogicSigArgSize))
return
}
(*z).Args[zb0001], bts, err = msgp.ReadBytesBytes(bts, (*z).Args[zb0001])
@@ -3444,7 +3444,7 @@ func (z *LogicSig) MsgIsZero() bool {
func LogicSigMaxSize() (s int) {
s = 1 + 2 + msgp.BytesPrefixSize + config.MaxLogicSigMaxSize + 4 + crypto.SignatureMaxSize() + 5 + crypto.MultisigSigMaxSize() + 4
// Calculating size of slice: z.Args
- s += msgp.ArrayHeaderSize + ((EvalMaxArgs) * (msgp.BytesPrefixSize + config.MaxLogicSigMaxSize))
+ s += msgp.ArrayHeaderSize + config.MaxLogicSigMaxSize
return
}
diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go
index d0f38bca90..46d3c4cf7e 100644
--- a/data/transactions/verify/txn.go
+++ b/data/transactions/verify/txn.go
@@ -92,7 +92,7 @@ type TxGroupErrorReason int
const (
// TxGroupErrorReasonGeneric is a generic (not tracked) reason code
TxGroupErrorReasonGeneric TxGroupErrorReason = iota
- // TxGroupErrorReasonNotWellFormed is txn.WellFormed failure
+ // TxGroupErrorReasonNotWellFormed is txn.WellFormed failure or malformed logic signature
TxGroupErrorReasonNotWellFormed
// TxGroupErrorReasonInvalidFee is invalid fee pooling in transaction group
TxGroupErrorReasonInvalidFee
@@ -213,6 +213,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl
minFeeCount := uint64(0)
feesPaid := uint64(0)
+ lSigPooledSize := 0
for i, stxn := range stxs {
prepErr := txnBatchPrep(i, groupCtx, verifier)
if prepErr != nil {
@@ -224,6 +225,17 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl
minFeeCount++
}
feesPaid = basics.AddSaturate(feesPaid, stxn.Txn.Fee.Raw)
+ lSigPooledSize += stxn.Lsig.Len()
+ }
+ if groupCtx.consensusParams.EnableLogicSigSizePooling {
+ lSigMaxPooledSize := len(stxs) * int(groupCtx.consensusParams.LogicSigMaxSize)
+ if lSigPooledSize > lSigMaxPooledSize {
+ errorMsg := fmt.Errorf(
+ "txgroup had %d bytes of LogicSigs, more than the available pool of %d bytes",
+ lSigPooledSize, lSigMaxPooledSize,
+ )
+ return nil, &TxGroupError{err: errorMsg, GroupIndex: -1, Reason: TxGroupErrorReasonNotWellFormed}
+ }
}
feeNeeded, overflow := basics.OMul(groupCtx.consensusParams.MinTxnFee, minFeeCount)
if overflow {
@@ -360,8 +372,8 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier
if version > groupCtx.consensusParams.LogicSigVersion {
return errors.New("LogicSig.Logic version too new")
}
- if uint64(lsig.Len()) > groupCtx.consensusParams.LogicSigMaxSize {
- return errors.New("LogicSig.Logic too long")
+ if !groupCtx.consensusParams.EnableLogicSigSizePooling && uint64(lsig.Len()) > groupCtx.consensusParams.LogicSigMaxSize {
+ return errors.New("LogicSig too long")
}
err := logic.CheckSignature(gi, groupCtx.evalParams)
diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go
index 282a031097..192a87e412 100644
--- a/data/transactions/verify/txn_test.go
+++ b/data/transactions/verify/txn_test.go
@@ -649,6 +649,70 @@ func BenchmarkPaysetGroups(b *testing.B) {
b.StopTimer()
}
+func TestLsigSize(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ secrets, addresses, _ := generateAccounts(2)
+
+ execPool := execpool.MakePool(t)
+ verificationPool := execpool.MakeBacklog(execPool, 64, execpool.LowPriority, t)
+ defer verificationPool.Shutdown()
+
+ // From consensus version 18, we have lsigs with a maximum size of 1000 bytes.
+ // We need to use pragma 1 for teal in v18
+ pragma := uint(1)
+ consensusVersionPreSizePooling := protocol.ConsensusV18
+ consensusVersionPostSizePooling := protocol.ConsensusFuture
+
+ // We will do tests based on a transaction group of 2 payment transactions,
+ // the first signed by a lsig and the second a vanilla payment transaction.
+ testCases := []struct {
+ consensusVersion protocol.ConsensusVersion
+ lsigSize uint
+ success bool
+ }{
+ {consensusVersionPreSizePooling, 1000, true},
+ {consensusVersionPreSizePooling, 1001, false},
+ {consensusVersionPostSizePooling, 2000, true},
+ {consensusVersionPostSizePooling, 2001, false},
+ }
+
+ blkHdr := createDummyBlockHeader()
+ for _, test := range testCases {
+ blkHdr.UpgradeState.CurrentProtocol = test.consensusVersion
+
+ lsig, err := txntest.GenerateProgramOfSize(test.lsigSize, pragma)
+ require.NoError(t, err)
+
+ lsigPay := txntest.Txn{
+ Type: protocol.PaymentTx,
+ Sender: basics.Address(logic.HashProgram(lsig)),
+ Receiver: addresses[0],
+ Fee: config.Consensus[test.consensusVersion].MinTxnFee,
+ }
+
+ vanillaPay := txntest.Txn{
+ Type: protocol.PaymentTx,
+ Sender: addresses[0],
+ Receiver: addresses[1],
+ Fee: config.Consensus[test.consensusVersion].MinTxnFee,
+ }
+
+ group := txntest.Group(&lsigPay, &vanillaPay)
+ group[0].Lsig = transactions.LogicSig{
+ Logic: lsig,
+ }
+ group[1].Sig = secrets[0].Sign(group[1].Txn)
+
+ err = PaysetGroups(context.Background(), [][]transactions.SignedTxn{group}, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), &DummyLedgerForSignature{})
+ if test.success {
+ require.NoError(t, err)
+ } else {
+ require.Error(t, err)
+ }
+ }
+}
+
func TestTxnGroupMixedSignatures(t *testing.T) {
partitiontest.PartitionTest(t)
diff --git a/data/txntest/program.go b/data/txntest/program.go
new file mode 100644
index 0000000000..f0fa63dd81
--- /dev/null
+++ b/data/txntest/program.go
@@ -0,0 +1,51 @@
+// Copyright (C) 2019-2024 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package txntest
+
+import (
+ "fmt"
+
+ "github.com/algorand/go-algorand/data/transactions/logic"
+)
+
+// GenerateProgramOfSize return a TEAL bytecode of `size` bytes which always succeeds.
+// `size` must be at least 9 bytes
+func GenerateProgramOfSize(size uint, pragma uint) ([]byte, error) {
+ if size < 9 {
+ return nil, fmt.Errorf("size must be at least 9 bytes; got %d", size)
+ }
+ ls := fmt.Sprintf("#pragma version %d\n", pragma)
+ if size%2 == 0 {
+ ls += "int 10\npop\nint 1\npop\n"
+ } else {
+ ls += "int 1\npop\nint 1\npop\n"
+ }
+ for i := uint(11); i <= size; i += 2 {
+ ls = ls + "int 1\npop\n"
+ }
+ ls = ls + "int 1"
+ code, err := logic.AssembleString(ls)
+ if err != nil {
+ return nil, err
+ }
+ // panic if the function is not working as expected and needs to be updated
+ if len(code.Program) != int(size) {
+ panic(fmt.Sprintf("wanted to create a program of size %d but got a program of size %d",
+ size, len(code.Program)))
+ }
+ return code.Program, nil
+}
diff --git a/node/node_test.go b/node/node_test.go
index 387ea58aa8..664f115482 100644
--- a/node/node_test.go
+++ b/node/node_test.go
@@ -819,10 +819,10 @@ func TestMaxSizesCorrect(t *testing.T) {
maxCombinedTxnSize := uint64(transactions.SignedTxnMaxSize())
// subtract out the two smaller signature sizes (logicsig is biggest, it can *contain* the others)
maxCombinedTxnSize -= uint64(crypto.SignatureMaxSize() + crypto.MultisigSigMaxSize())
- // the logicsig size is *also* an overestimate, because it thinks each
- // logicsig arg can be big, but really the sum of the args and the program
- // has a max size.
- maxCombinedTxnSize -= uint64(transactions.EvalMaxArgs * config.MaxLogicSigMaxSize)
+ // the logicsig size is *also* an overestimate, because it thinks that the logicsig and
+ // the logicsig args can both be up to to MaxLogicSigMaxSize, but that's the max for
+ // them combined, so it double counts and we have to subtract one.
+ maxCombinedTxnSize -= uint64(config.MaxLogicSigMaxSize)
// maxCombinedTxnSize is still an overestimate because it assumes all txn
// type fields can be in the same txn. That's not true, but it provides an
diff --git a/test/e2e-go/cli/goal/expect/tealConsensusTest.exp b/test/e2e-go/cli/goal/expect/tealConsensusTest.exp
index a4231acd96..9df3d4abe9 100644
--- a/test/e2e-go/cli/goal/expect/tealConsensusTest.exp
+++ b/test/e2e-go/cli/goal/expect/tealConsensusTest.exp
@@ -54,15 +54,6 @@ if { [catch {
"\n" { ::AlgorandGoal::Abort $expect_out(buffer) }
}
- teal "$TEST_ROOT_DIR/big-sig.teal" 2 1001 1
- spawn goal clerk compile "$TEST_ROOT_DIR/big-sig.teal"
- expect {
- -re {[A-Z2-9]{58}} { ::AlgorandGoal::Abort "hash" }
- -re {.*logicsig program size too large} { puts "bigsigcheck: pass" }
- eof { ::AlgorandGoal::Abort $expect_out(buffer) }
- "\n" { ::AlgorandGoal::Abort $expect_out(buffer) }
- }
-
teal "$TEST_ROOT_DIR/barely-fits-app.teal" 2 4090 1 "int 0\nbalance\npop\n"
spawn goal clerk compile "$TEST_ROOT_DIR/barely-fits-app.teal"
expect {
@@ -71,16 +62,6 @@ if { [catch {
"\n" { ::AlgorandGoal::Abort $expect_out(buffer) }
}
- # MaxAppProgramLen = 2K * (1 + 3 pages max)
- teal "$TEST_ROOT_DIR/big-app.teal" 2 8192 1 "int 0\nbalance\npop\n"
- spawn goal clerk compile "$TEST_ROOT_DIR/big-app.teal"
- expect {
- -re {[A-Z2-9]{58}} { ::AlgorandGoal::Abort "hash" }
- -re {.*app program size too large} { puts "bigappcheck: pass" }
- eof { ::AlgorandGoal::Abort $expect_out(buffer) }
- "\n" { ::AlgorandGoal::Abort $expect_out(buffer) }
- }
-
# Test cost limits during dryrun
exec goal clerk send -F "$TEST_ROOT_DIR/small-sig.teal" -t GXBNLU4AXQABPLHXJDMTG2YXSDT4EWUZACT7KTPFXDQW52XPTIUS5OZ5HQ -a 100 -d $TEST_PRIMARY_NODE_DIR -o $TEST_ROOT_DIR/small-sig.tx
spawn goal clerk dryrun -t $TEST_ROOT_DIR/small-sig.tx
diff --git a/test/e2e-go/features/transactions/logicsig_test.go b/test/e2e-go/features/transactions/logicsig_test.go
new file mode 100644
index 0000000000..8958b5561f
--- /dev/null
+++ b/test/e2e-go/features/transactions/logicsig_test.go
@@ -0,0 +1,138 @@
+// Copyright (C) 2019-2024 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package transactions
+
+import (
+ "path/filepath"
+ "testing"
+
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/txntest"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+func TestLogicSigSizeBeforePooling(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+ a := require.New(fixtures.SynchronizedTest(t))
+
+ // From consensus version 18, we have lsigs with a maximum size of 1000 bytes.
+ // We need to use pragma 1 for teal in v18
+ pragma := uint(1)
+ tealOK, err := txntest.GenerateProgramOfSize(1000, pragma)
+ a.NoError(err)
+ tealTooLong, err := txntest.GenerateProgramOfSize(1001, pragma)
+ a.NoError(err)
+
+ testLogicSize(t, tealOK, tealTooLong, filepath.Join("nettemplates", "TwoNodes50EachV18.json"))
+}
+
+func TestLogicSigSizeAfterPooling(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+ a := require.New(fixtures.SynchronizedTest(t))
+
+ pragma := uint(1)
+ tealOK, err := txntest.GenerateProgramOfSize(2000, pragma)
+ a.NoError(err)
+ tealTooLong, err := txntest.GenerateProgramOfSize(2001, pragma)
+ a.NoError(err)
+
+ // TODO: Update this when lsig pooling graduates from vFuture
+ testLogicSize(t, tealOK, tealTooLong, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+}
+
+// testLogicSize takes two TEAL programs, one expected to be ok and one expected to be too long
+// and thus to be rejected, and tests that's indeed the case.
+func testLogicSize(t *testing.T, tealOK, tealTooLong []byte,
+ networkTemplate string) {
+
+ t.Parallel()
+ a := require.New(fixtures.SynchronizedTest(t))
+
+ var fixture fixtures.RestClientFixture
+ fixture.Setup(t, networkTemplate)
+ defer fixture.Shutdown()
+
+ client := fixture.LibGoalClient
+ accountList, err := fixture.GetWalletsSortedByBalance()
+ a.NoError(err)
+ baseAcct := accountList[0].Address
+
+ walletHandler, err := client.GetUnencryptedWalletHandle()
+ a.NoError(err)
+
+ signatureOK, err := client.SignProgramWithWallet(walletHandler, nil, baseAcct, tealOK)
+ a.NoError(err)
+ lsigOK := transactions.LogicSig{Logic: tealOK, Sig: signatureOK}
+
+ signatureTooLong, err := client.SignProgramWithWallet(walletHandler, nil, baseAcct, tealTooLong)
+ a.NoError(err)
+ lsigTooLong := transactions.LogicSig{Logic: tealTooLong, Sig: signatureTooLong}
+
+ // We build two transaction groups of two transactions each.
+ // The first txn will be either signed by an ok lsig or a too long one.
+ // The second is a vanilla payment transaction to complete the group.
+ var txn1Success, txn2Success, txn1Fail, txn2Fail transactions.Transaction
+ for i, txn := range []*transactions.Transaction{&txn1Success, &txn2Success, &txn1Fail, &txn2Fail} {
+ *txn, err = client.ConstructPayment(baseAcct, baseAcct, 0, uint64(i), nil, "", [32]byte{}, 0, 0)
+ a.NoError(err)
+ }
+
+ // success group
+ gidSuccess, err := client.GroupID([]transactions.Transaction{txn1Success, txn2Success})
+ a.NoError(err)
+
+ txn1Success.Group = gidSuccess
+ stxn1Success := transactions.SignedTxn{Txn: txn1Success, Lsig: lsigOK}
+
+ txn2Success.Group = gidSuccess
+ stxn2Success, err := client.SignTransactionWithWallet(walletHandler, nil, txn2Success)
+ a.NoError(err)
+
+ err = client.BroadcastTransactionGroup([]transactions.SignedTxn{stxn1Success, stxn2Success})
+ a.NoError(err)
+
+ // fail group
+ gidFail, err := client.GroupID([]transactions.Transaction{txn1Fail, txn2Fail})
+ a.NoError(err)
+
+ txn1Fail.Group = gidFail
+ stxn1Fail := transactions.SignedTxn{Txn: txn1Fail, Lsig: lsigTooLong}
+
+ txn2Fail.Group = gidFail
+ stxn2Fail, err := client.SignTransactionWithWallet(walletHandler, nil, txn2Fail)
+ a.NoError(err)
+
+ cp, err := client.ConsensusParams(0)
+ a.NoError(err)
+
+ err = client.BroadcastTransactionGroup([]transactions.SignedTxn{stxn1Fail, stxn2Fail})
+ if cp.EnableLogicSigSizePooling {
+ a.ErrorContains(err, "more than the available pool")
+ } else {
+ a.ErrorContains(err, "LogicSig too long")
+ }
+
+ // wait for the second transaction in the successful group to confirm
+ txn2SuccessID := txn2Success.ID().String()
+ _, curRound := fixture.GetBalanceAndRound(baseAcct)
+ confirmed := fixture.WaitForTxnConfirmation(curRound+5, txn2SuccessID)
+ a.True(confirmed)
+}
diff --git a/test/testdata/nettemplates/TwoNodes50EachV18.json b/test/testdata/nettemplates/TwoNodes50EachV18.json
new file mode 100644
index 0000000000..198a4f0f99
--- /dev/null
+++ b/test/testdata/nettemplates/TwoNodes50EachV18.json
@@ -0,0 +1,36 @@
+{
+ "Genesis": {
+ "NetworkName": "tbd",
+ "ConsensusProtocol": "https://github.com/algorandfoundation/specs/tree/6c6bd668be0ab14098e51b37e806c509f7b7e31f",
+ "LastPartKeyRound": 3000,
+ "Wallets": [
+ {
+ "Name": "Wallet1",
+ "Stake": 50,
+ "Online": true
+ },
+ {
+ "Name": "Wallet2",
+ "Stake": 50,
+ "Online": true
+ }
+ ]
+ },
+ "Nodes": [
+ {
+ "Name": "Primary",
+ "IsRelay": true,
+ "Wallets": [
+ { "Name": "Wallet1",
+ "ParticipationOnly": false }
+ ]
+ },
+ {
+ "Name": "Node",
+ "Wallets": [
+ { "Name": "Wallet2",
+ "ParticipationOnly": false }
+ ]
+ }
+ ]
+}