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
5 changes: 3 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 Expand Up @@ -1162,6 +1162,7 @@ var dryrunCmd = &cobra.Command{
reportErrorf("program size too large: %d > %d", len(txn.Lsig.Logic), params.LogicSigMaxSize)
}
ep := logic.NewEvalParams(txgroup, &params, nil)
ep.SigLedger = logic.NoHeaderLedger{}
err := logic.CheckSignature(i, ep)
if err != nil {
reportErrorf("program failed Check: %s", err)
Expand Down
1 change: 1 addition & 0 deletions cmd/tealdbg/debugger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func TestDebuggerSimple(t *testing.T) {

ep := logic.NewEvalParams(make([]transactions.SignedTxnWithAD, 1), &proto, nil)
ep.Debugger = debugger
ep.SigLedger = logic.NoHeaderLedger{}

source := `int 0
int 1
Expand Down
1 change: 1 addition & 0 deletions cmd/tealdbg/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ func (r *LocalRunner) RunAll() error {
start := time.Now()

ep := logic.NewEvalParams(txngroup, &r.proto, &transactions.SpecialAddresses{})
ep.SigLedger = logic.NoHeaderLedger{}
configureDebugger(ep)

var last error
Expand Down
1 change: 1 addition & 0 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
if len(stxn.Lsig.Logic) > 0 {
var debug dryrunDebugReceiver
ep.Debugger = &debug
ep.SigLedger = &dl
pass, err := logic.EvalSignature(ti, ep)
var messages []string
result.Disassembly = debug.lines // Keep backwards compat
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
40 changes: 34 additions & 6 deletions data/transactions/logic/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,23 @@ 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)
}

// NoHeaderLedger is intended for debugging situations in which it is reasonable
// to preclude the use of `block` and `txn LastValidTime`
type NoHeaderLedger struct {
}

// BlockHdrCached always errors
func (NoHeaderLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access")
}

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

logger logging.Logger

Ledger LedgerForLogic
SigLedger LedgerForSignature
Ledger LedgerForLogic

// optional debugger
Debugger DebuggerHook
Expand Down Expand Up @@ -387,6 +405,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 +629,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 @@ -652,6 +674,9 @@ func EvalApp(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (b
// EvalSignature evaluates the logicsig of the ith transaction in params.
// A program passes successfully if it finishes with one int element on the stack that is non-zero.
func EvalSignature(gi int, params *EvalParams) (pass bool, err error) {
if params.SigLedger == nil {
return false, errors.New("no sig ledger in signature eval")
}
cx := EvalContext{
EvalParams: params,
runModeFlags: modeSig,
Expand Down Expand Up @@ -2304,7 +2329,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,10 +4873,13 @@ 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()
lastAvail := cx.txn.Txn.FirstValid - 1
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
if lastAvail > cx.txn.Txn.FirstValid { // txn had a 0 in FirstValid
lastAvail = 0 // So nothing will be available
}
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
round := basics.Round(r)
if round < firstAvail || round >= current {
return 0, fmt.Errorf("round %d is not available. It's outside [%d-%d]", r, firstAvail, current-1)
if firstAvail > round || round > lastAvail {
return 0, fmt.Errorf("round %d is not available. It's outside [%d-%d]", r, firstAvail, lastAvail)
}
return round, nil
}
Expand All @@ -4868,7 +4896,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
24 changes: 17 additions & 7 deletions 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 @@ -2372,7 +2373,7 @@ func TestReturnTypes(t *testing.T) {
"base64_decode": `: byte "YWJjMTIzIT8kKiYoKSctPUB+"; base64_decode StdEncoding`,
"json_ref": `: byte "{\"k\": 7}"; byte "k"; json_ref JSONUint64`,

"block": ": int 4294967200; block BlkSeed",
"block": "block BlkSeed",
}

/* Make sure the specialCmd tests the opcode in question */
Expand Down Expand Up @@ -2537,19 +2538,23 @@ 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
// 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.
// 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 fv, so we set the fv,lv more realistically.
txn.FirstValid = l.round() - 10
txn.LastValid = l.round() + 10

// Keep in mind that proto.MaxTxnLife is 1500 in the test proto

// l.round() is 0xffffffff+5 = 4294967300 in test ledger
testApp(t, "int 4294967299; block BlkSeed; len; int 32; ==", ep) // current - 1

// These first two tests show that current-1 is not available now, though a
// resonable extension is to allow such access for apps (not sigs).
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: resonable -> reasonable

testApp(t, "int 4294967299; block BlkSeed; len; int 32; ==", ep,
"not available") // current - 1
testApp(t, "int 4294967300; block BlkSeed; len; int 32; ==", ep,
"not available") // can't get current round's blockseed

// proto.MaxTxnLife is 1500 in test.
testApp(t, "int 4294967300; int 1500; -; block BlkSeed; len; int 32; ==", ep,
"not available") // 1500 back from current is more than 1500 back from lv
testApp(t, "int 4294967310; int 1500; -; block BlkSeed; len; int 32; ==", ep) // 1500 back from lv is legal
Expand All @@ -2560,6 +2565,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
12 changes: 8 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 Expand Up @@ -2689,6 +2692,7 @@ int 1`,
Proto: makeTestProto(),
TxnGroup: txgroup,
pastScratch: make([]*scratchSpace, 2),
SigLedger: MakeLedger(nil),
}

switch failCase.runMode {
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
Loading