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

AVM: Make txn FirstValidTime and block opcode available in logicsigs #4371

Merged
merged 12 commits into from
Aug 9, 2022
4 changes: 2 additions & 2 deletions cmd/goal/clerk.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ var sendCmd = &cobra.Command{
CurrentProtocol: proto,
},
}
groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, blockHeader)
groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, blockHeader, nil)
if err == nil {
err = verify.LogicSigSanityCheck(&uncheckedTxn, 0, groupCtx)
}
Expand Down Expand Up @@ -825,7 +825,7 @@ var signCmd = &cobra.Command{
}
var groupCtx *verify.GroupContext
if lsig.Logic != nil {
groupCtx, err = verify.PrepareGroupContext(txnGroup, contextHdr)
groupCtx, err = verify.PrepareGroupContext(txnGroup, contextHdr, nil)
if err != nil {
// this error has to be unsupported protocol
reportErrorf("%s: %v", txFilename, err)
Expand Down
2 changes: 1 addition & 1 deletion data/transactions/logic/assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2269,7 +2269,7 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) {
}
if strings.HasPrefix(spec.Name, "bytec_") {
b := spec.Name[len(spec.Name)-1] - byte('0')
if int(b) < len(dis.intc) {
if int(b) < len(dis.bytec) {
tzaffi marked this conversation as resolved.
Show resolved Hide resolved
out += fmt.Sprintf(" // %s", guessByteFormat(dis.bytec[b]))
}
}
Expand Down
21 changes: 17 additions & 4 deletions data/transactions/logic/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@ func ComputeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 {
return minVersion
}

// LedgerForSignature represents the parts of Ledger that LogicSigs can see. It
// only exposes things that consensus has already agreed upon, so it is
// "stateless" for signature purposes.
type LedgerForSignature interface {
BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error)
Round() basics.Round // don't expose the value to programs
}

// LedgerForLogic represents ledger API for Stateful TEAL program
type LedgerForLogic interface {
AccountData(addr basics.Address) (ledgercore.AccountData, error)
Expand Down Expand Up @@ -239,7 +247,8 @@ type EvalParams struct {

logger logging.Logger

Ledger LedgerForLogic
SigLedger LedgerForSignature
Ledger LedgerForLogic

// optional debugger
Debugger DebuggerHook
Expand Down Expand Up @@ -387,6 +396,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext)
Specials: caller.Specials,
PooledApplicationBudget: caller.PooledApplicationBudget,
pooledAllowedInners: caller.pooledAllowedInners,
SigLedger: caller.SigLedger,
Ledger: caller.Ledger,
created: caller.created,
appAddrCache: caller.appAddrCache,
Expand Down Expand Up @@ -610,6 +620,9 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam
if params.Ledger == nil {
return false, nil, errors.New("no ledger in contract eval")
}
if params.SigLedger == nil {
params.SigLedger = params.Ledger
algochoi marked this conversation as resolved.
Show resolved Hide resolved
}
if aid == 0 {
return false, nil, errors.New("0 appId in contract eval")
}
Expand Down Expand Up @@ -2304,7 +2317,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t
if err != nil {
return sv, err
}
hdr, err := cx.Ledger.BlockHdrCached(rnd)
hdr, err := cx.SigLedger.BlockHdrCached(rnd)
if err != nil {
return sv, err
}
Expand Down Expand Up @@ -4848,7 +4861,7 @@ func (cx *EvalContext) availableRound(r uint64) (basics.Round, error) {
if firstAvail > cx.txn.Txn.LastValid || firstAvail == 0 { // early in chain's life
firstAvail = 1
}
current := cx.Ledger.Round()
current := cx.SigLedger.Round()
round := basics.Round(r)
if round < firstAvail || round >= current {
Copy link
Contributor

Choose a reason for hiding this comment

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

this is interesting, Round() is either current, or latest, depending on a context, and the condition round >= current is technically correct for both contexts but looks as a maintainability problem.

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've changed it so that regardless of how the SigLedger is supplied (either as a copy of Ledger, or on its own for LogicSig evaluation) Round() is Latest()+1. I think that is fairly good, although I remain worried about transactions added to a long queue that will be dropped asking for a header that doesn't exist yet.

return 0, fmt.Errorf("round %d is not available. It's outside [%d-%d]", r, firstAvail, current-1)
Expand All @@ -4868,7 +4881,7 @@ func opBlock(cx *EvalContext) error {
return fmt.Errorf("invalid block field %s", f)
}

hdr, err := cx.Ledger.BlockHdrCached(round)
hdr, err := cx.SigLedger.BlockHdrCached(round)
if err != nil {
return err
}
Expand Down
8 changes: 7 additions & 1 deletion data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func makeSampleEnvWithVersion(version uint64) (*EvalParams, *transactions.Transa
ep := defaultEvalParamsWithVersion(nil, version)
ep.TxnGroup = transactions.WrapSignedTxnsWithAD(makeSampleTxnGroup(makeSampleTxn()))
ledger := MakeLedger(map[basics.Address]uint64{})
ep.SigLedger = ledger
ep.Ledger = ledger
return ep, &ep.TxnGroup[0].Txn, ledger
}
Expand Down Expand Up @@ -2537,7 +2538,7 @@ func TestLatestTimestamp(t *testing.T) {
func TestBlockSeed(t *testing.T) {
ep, txn, l := makeSampleEnv()

// makeSampleENv creates txns with fv, lv that don't actually fit the round
// makeSampleEnv creates txns with fv, lv that don't actually fit the round
// in l. Nothing in most tests cares. But the rule for `block` is
// related to lv and the current round, so we set the fv,lv more
// realistically.
Expand All @@ -2560,6 +2561,11 @@ func TestBlockSeed(t *testing.T) {
// A little silly, as it only tests the test ledger: ensure samenes and differentness
testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff0; block BlkSeed; ==", ep)
testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff1; block BlkSeed; !=", ep)

// `block` should also work in LogicSigs, to drive home the point, blot out
// the normal Ledger
ep.Ledger = nil
testLogic(t, "int 0xfffffff0; block BlkTimestamp", randomnessVersion, ep)
}

func TestCurrentApplicationID(t *testing.T) {
Expand Down
11 changes: 7 additions & 4 deletions data/transactions/logic/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) *
Specials: &transactions.SpecialAddresses{},
Trace: &strings.Builder{},
FeeCredit: &zero,
SigLedger: MakeLedger(nil),
}
if txn != nil {
ep.TxnGroup[0].SignedTxn = *txn
Expand Down Expand Up @@ -252,16 +253,18 @@ func TestTxnFirstValidTime(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

// txn FirstValidTime is unusual. It's not really a field of a txn, but
// since it looks at the past of the blockchain, it is "stateless", in the
// sense that the value can not change, so it is available in logicsigs

ep, tx, ledger := makeSampleEnv()

// By default, test ledger uses an oddball round, ask it what round it's
// going to use and prep fv, lv accordingly.
current := ledger.Round()

// txn FirstValidTime is unusual. It's not really a field of a txn, but
// since it looks at the past of the blockchain, it is "stateless"

// Kill off ep.Ledger, to confirm it's not being used
ep.Ledger = nil

tx.FirstValid = current - 10
tx.LastValid = current + 10
testLogic(t, "txn FirstValidTime", 7, ep)
Expand Down
18 changes: 11 additions & 7 deletions data/transactions/verify/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ type GroupContext struct {
consensusParams config.ConsensusParams
minAvmVersion uint64
signedGroupTxns []transactions.SignedTxn
ledger logic.LedgerForSignature
}

// PrepareGroupContext prepares a verification group parameter object for a given transaction
// group.
func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader) (*GroupContext, error) {
func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, ledger logic.LedgerForSignature) (*GroupContext, error) {
if len(group) == 0 {
return nil, nil
}
Expand All @@ -87,6 +88,7 @@ func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.
consensusParams: consensusParams,
minAvmVersion: logic.ComputeMinAvmVersion(transactions.WrapSignedTxnsWithAD(group)),
signedGroupTxns: group,
ledger: ledger,
}, nil
}

Expand Down Expand Up @@ -132,10 +134,10 @@ func TxnBatchVerify(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex
}

// TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data.
func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache) (groupCtx *GroupContext, err error) {
func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (groupCtx *GroupContext, err error) {
batchVerifier := crypto.MakeBatchVerifier()

if groupCtx, err = TxnGroupBatchVerify(stxs, contextHdr, cache, batchVerifier); err != nil {
if groupCtx, err = TxnGroupBatchVerify(stxs, contextHdr, cache, ledger, batchVerifier); err != nil {
return nil, err
}

Expand All @@ -152,8 +154,8 @@ func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader,

// TxnGroupBatchVerify verifies a []SignedTxn having no obviously inconsistent data.
// it is the caller responsibility to call batchVerifier.verify()
func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, verifier *crypto.BatchVerifier) (groupCtx *GroupContext, err error) {
groupCtx, err = PrepareGroupContext(stxs, contextHdr)
func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier) (groupCtx *GroupContext, err error) {
groupCtx, err = PrepareGroupContext(stxs, contextHdr, ledger)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -294,6 +296,7 @@ func LogicSigSanityCheckBatchVerify(txn *transactions.SignedTxn, groupIndex int,
Proto: &groupCtx.consensusParams,
TxnGroup: txngroup,
MinAvmVersion: &groupCtx.minAvmVersion,
SigLedger: groupCtx.ledger, // won't be needed for CheckSignature
}
err := logic.CheckSignature(groupIndex, &ep)
if err != nil {
Expand Down Expand Up @@ -349,6 +352,7 @@ func logicSigBatchVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *
Proto: &groupCtx.consensusParams,
TxnGroup: transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns),
MinAvmVersion: &groupCtx.minAvmVersion,
SigLedger: groupCtx.ledger,
}
pass, err := logic.EvalSignature(groupIndex, &ep)
if err != nil {
Expand All @@ -370,7 +374,7 @@ func logicSigBatchVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *
// a PaysetGroups may be well-formed, but a payset might contain an overspend.
//
// This version of verify is performing the verification over the provided execution pool.
func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHeader bookkeeping.BlockHeader, verificationPool execpool.BacklogPool, cache VerifiedTransactionCache) (err error) {
func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHeader bookkeeping.BlockHeader, verificationPool execpool.BacklogPool, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (err error) {
if len(payset) == 0 {
return nil
}
Expand Down Expand Up @@ -407,7 +411,7 @@ func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHea

batchVerifier := crypto.MakeBatchVerifierWithHint(len(payset))
for i, signTxnsGrp := range txnGroups {
groupCtxs[i], grpErr = TxnGroupBatchVerify(signTxnsGrp, blkHeader, nil, batchVerifier)
groupCtxs[i], grpErr = TxnGroupBatchVerify(signTxnsGrp, blkHeader, nil, ledger, batchVerifier)
// abort only if it's a non-cache error.
if grpErr != nil {
return grpErr
Expand Down
22 changes: 11 additions & 11 deletions data/transactions/verify/txn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func TestSignedPayment(t *testing.T) {
payments, stxns, secrets, addrs := generateTestObjects(1, 1, 0)
payment, stxn, secret, addr := payments[0], stxns[0], secrets[0], addrs[0]

groupCtx, err := PrepareGroupContext(stxns, blockHeader)
groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil)
require.NoError(t, err)
require.NoError(t, payment.WellFormed(spec, proto), "generateTestObjects generated an invalid payment")
require.NoError(t, Txn(&stxn, 0, groupCtx), "generateTestObjects generated a bad signedtxn")
Expand All @@ -135,7 +135,7 @@ func TestTxnValidationEncodeDecode(t *testing.T) {
_, signed, _, _ := generateTestObjects(100, 50, 0)

for _, txn := range signed {
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader)
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil)
require.NoError(t, err)
if Txn(&txn, 0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
Expand All @@ -157,7 +157,7 @@ func TestTxnValidationEmptySig(t *testing.T) {
_, signed, _, _ := generateTestObjects(100, 50, 0)

for _, txn := range signed {
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader)
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil)
require.NoError(t, err)
if Txn(&txn, 0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
Expand Down Expand Up @@ -202,7 +202,7 @@ func TestTxnValidationCompactCert(t *testing.T) {
},
}

groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader)
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil)
require.NoError(t, err)

err = Txn(&stxn, 0, groupCtx)
Expand Down Expand Up @@ -256,7 +256,7 @@ func TestDecodeNil(t *testing.T) {
err := protocol.Decode(nilEncoding, &st)
if err == nil {
// This used to panic when run on a zero value of SignedTxn.
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader)
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil)
require.NoError(t, err)
Txn(&st, 0, groupCtx)
}
Expand Down Expand Up @@ -285,17 +285,17 @@ func TestPaysetGroups(t *testing.T) {
txnGroups := generateTransactionGroups(signedTxn, secrets, addrs)

startPaysetGroupsTime := time.Now()
err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000))
err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil)
require.NoError(t, err)
paysetGroupDuration := time.Now().Sub(startPaysetGroupsTime)

// break the signature and see if it fails.
txnGroups[0][0].Sig[0] = txnGroups[0][0].Sig[0] + 1
err = PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000))
err = PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil)
require.Error(t, err)

// ensure the rest are fine
err = PaysetGroups(context.Background(), txnGroups[1:], blkHdr, verificationPool, MakeVerifiedTransactionCache(50000))
err = PaysetGroups(context.Background(), txnGroups[1:], blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil)
require.NoError(t, err)

// test the context cancelation:
Expand All @@ -312,7 +312,7 @@ func TestPaysetGroups(t *testing.T) {
go func() {
defer close(waitCh)
cache := MakeVerifiedTransactionCache(50000)
waitCh <- PaysetGroups(ctx, txnGroups, blkHdr, verificationPool, cache)
waitCh <- PaysetGroups(ctx, txnGroups, blkHdr, verificationPool, cache, nil)
}()
startPaysetGroupsTime = time.Now()
select {
Expand Down Expand Up @@ -366,7 +366,7 @@ func BenchmarkPaysetGroups(b *testing.B) {
cache := MakeVerifiedTransactionCache(50000)

b.ResetTimer()
err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, cache)
err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, cache, nil)
require.NoError(b, err)
b.StopTimer()
}
Expand Down Expand Up @@ -422,7 +422,7 @@ func BenchmarkTxn(b *testing.B) {

b.ResetTimer()
for _, txnGroup := range txnGroups {
groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader)
groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader, nil)
require.NoError(b, err)
for i, txn := range txnGroup {
err := Txn(&txn, i, groupCtx)
Expand Down
12 changes: 6 additions & 6 deletions data/transactions/verify/verifiedTxnCache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestAddingToCache(t *testing.T) {
impl := icache.(*verifiedTransactionCache)
_, signedTxn, secrets, addrs := generateTestObjects(10, 5, 50)
txnGroups := generateTransactionGroups(signedTxn, secrets, addrs)
groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader)
groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil)
require.NoError(t, err)
impl.Add(txnGroups[0], groupCtx)
// make it was added.
Expand All @@ -55,7 +55,7 @@ func TestBucketCycling(t *testing.T) {
_, signedTxn, _, _ := generateTestObjects(entriesPerBucket*bucketCount*2, bucketCount, 0)

require.Equal(t, entriesPerBucket*bucketCount*2, len(signedTxn))
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader)
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader, nil)
require.NoError(t, err)

// fill up the cache with entries.
Expand Down Expand Up @@ -92,7 +92,7 @@ func TestGetUnverifiedTranscationGroups50(t *testing.T) {
if i%2 == 0 {
expectedUnverifiedGroups = append(expectedUnverifiedGroups, txnGroups[i])
} else {
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader)
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
impl.Add(txnGroups[i], groupCtx)
}
}
Expand All @@ -116,7 +116,7 @@ func BenchmarkGetUnverifiedTranscationGroups50(b *testing.B) {
if i%2 == 1 {
queryTxnGroups = append(queryTxnGroups, txnGroups[i])
} else {
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader)
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
impl.Add(txnGroups[i], groupCtx)
}
}
Expand Down Expand Up @@ -145,7 +145,7 @@ func TestUpdatePinned(t *testing.T) {

// insert some entries.
for i := 0; i < len(txnGroups); i++ {
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader)
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
impl.Add(txnGroups[i], groupCtx)
}

Expand Down Expand Up @@ -174,7 +174,7 @@ func TestPinningTransactions(t *testing.T) {

// insert half of the entries.
for i := 0; i < len(txnGroups)/2; i++ {
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader)
groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
impl.Add(txnGroups[i], groupCtx)
}

Expand Down
Loading