Skip to content

Commit

Permalink
AVM: Add global GenesisHash
Browse files Browse the repository at this point in the history
Paranoid code (especially logicsigs) can check if they are on testnet.
  • Loading branch information
jannotti committed Dec 7, 2023
1 parent ed278b8 commit d7edd37
Show file tree
Hide file tree
Showing 18 changed files with 173 additions and 23 deletions.
5 changes: 5 additions & 0 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
Expand Down Expand Up @@ -241,6 +242,10 @@ func (dl *dryrunLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
return bookkeeping.BlockHeader{}, nil
}

func (dl *dryrunLedger) GenesisHash() crypto.Digest {
return crypto.Digest{}
}

func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
return nil
}
Expand Down
1 change: 1 addition & 0 deletions data/transactions/logic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ Global fields are fields that are common to all the transactions in the group. I
| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. |
| 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. |
| 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. |
| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. |


**Asset Fields**
Expand Down
1 change: 1 addition & 0 deletions data/transactions/logic/TEAL_opcodes_v10.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Fields
| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. |
| 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. |
| 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. |
| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. |


## gtxn
Expand Down
18 changes: 10 additions & 8 deletions data/transactions/logic/assembler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1686,17 +1686,19 @@ txn NumApprovalProgramPages
txna ApprovalProgramPages 0
txn NumClearStateProgramPages
txna ClearStateProgramPages 0
pushint 1
block BlkTimestamp
pushint 1
block BlkSeed
global AssetCreateMinBalance
global AssetOptInMinBalance
global GenesisHash
`, AssemblerMaxVersion)
for _, globalField := range GlobalFieldNames {
if !strings.Contains(text, globalField) {
t.Errorf("TestAssembleDisassemble missing field global %v", globalField)
}
}
for _, txnField := range TxnFieldNames {
if !strings.Contains(text, txnField) {
t.Errorf("TestAssembleDisassemble missing field txn %v", txnField)
for _, names := range [][]string{GlobalFieldNames[:], TxnFieldNames[:], blockFieldNames[:]} {
for _, f := range names {
if !strings.Contains(text, f) {
t.Errorf("TestAssembleDisassemble missing field %v", f)
}
}
}
ops := testProg(t, text, AssemblerMaxVersion)
Expand Down
23 changes: 19 additions & 4 deletions data/transactions/logic/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,12 @@ func computeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 {
// "stateless" for signature purposes.
type LedgerForSignature interface {
BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
GenesisHash() crypto.Digest
}

// NoHeaderLedger is intended for debugging situations in which it is reasonable
// to preclude the use of `block` and `txn LastValidTime`
// NoHeaderLedger is intended for debugging TEAL in isolation(no real ledger) in
// which it is reasonable to preclude the use of `block`, `txn
// LastValidTime`. Also `global GenesisHash` is just a static value.
type NoHeaderLedger struct {
}

Expand All @@ -212,6 +214,16 @@ func (NoHeaderLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access")
}

// GenesisHash returns a fixed value
func (NoHeaderLedger) GenesisHash() crypto.Digest {
return crypto.Digest{
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
}
}

// LedgerForLogic represents ledger API for Stateful TEAL program
type LedgerForLogic interface {
AccountData(addr basics.Address) (ledgercore.AccountData, error)
Expand Down Expand Up @@ -3610,8 +3622,11 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er
sv.Uint = cx.Proto.MinBalance
case AssetOptInMinBalance:
sv.Uint = cx.Proto.MinBalance
case GenesisHash:
gh := cx.SigLedger.GenesisHash()
sv.Bytes = gh[:]
default:
err = fmt.Errorf("invalid global field %d", fs.field)
return sv, fmt.Errorf("invalid global field %s", fs.field)
}

if fs.ftype.AVMType != sv.avmType() {
Expand Down Expand Up @@ -5587,7 +5602,7 @@ func opBlock(cx *EvalContext) error {
cx.Stack[last].Uint = uint64(hdr.TimeStamp)
return nil
default:
return fmt.Errorf("invalid block field %d", fs.field)
return fmt.Errorf("invalid block field %s", fs.field)
}
}

Expand Down
29 changes: 29 additions & 0 deletions data/transactions/logic/evalAppTxn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,35 @@ func TestBadField(t *testing.T) {
TestAppBytes(t, ops.Program, ep, "invalid itxn_field FirstValid")
}

// TestInnerValidity logs fv and lv fields that are handled oddly (valid
// rounds are copied) so we can check if they are correct.
func TestInnerValidity(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
ep, tx, ledger := MakeSampleEnv()
tx.GenesisHash = crypto.Digest{0x01, 0x02, 0x03}
logger := TestProg(t, `
txn FirstValid; itob; log;
txn LastValid; itob; log;
int 1`, AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: logger.Program,
})

ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
itxn_submit
itxn Logs 0; btoi; txn FirstValid; ==; assert
itxn Logs 1; btoi; txn LastValid; ==; assert
int 1
`, ep)

}

func TestNumInnerShallow(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
Expand Down
10 changes: 9 additions & 1 deletion data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3435,6 +3435,14 @@ func TestLatestTimestamp(t *testing.T) {
testApp(t, source, ep)
}

func TestGenHash(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
ep, _, _ := makeSampleEnv()
source := "global GenesisHash; byte 0x030203; int 29; bzero; concat; =="
testApp(t, source, ep)
}

func TestBlockSeed(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
Expand Down Expand Up @@ -3465,7 +3473,7 @@ func TestBlockSeed(t *testing.T) {
testApp(t, "int 4294967310; int 1502; -; block BlkSeed; len; int 32; ==", ep,
"not available") // 1501 back from lv is not

// A little silly, as it only tests the test ledger: ensure samenes and differentness
// A little silly, as it only tests the test ledger: ensure sameness and differentness
testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff0; block BlkSeed; ==", ep)
testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff1; block BlkSeed; !=", ep)

Expand Down
5 changes: 5 additions & 0 deletions data/transactions/logic/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,9 @@ const (
// AssetOptInMinBalance is the additional minimum balance required to opt in to an asset
AssetOptInMinBalance

// GenesisHash is the genesis hash for the network
GenesisHash

invalidGlobalField // compile-time constant for number of fields
)

Expand Down Expand Up @@ -599,6 +602,7 @@ var globalFieldSpecs = [...]globalFieldSpec{
"The additional minimum balance required to create (and opt-in to) an asset."},
{AssetOptInMinBalance, StackUint64, modeAny, 10,
"The additional minimum balance required to opt-in to an asset."},
{GenesisHash, StackBytes32, modeAny, 10, "The Genesis Hash for the network."},
}

func globalFieldSpecByField(f GlobalField) (globalFieldSpec, bool) {
Expand Down Expand Up @@ -961,6 +965,7 @@ const (
BlkSeed BlockField = iota
// BlkTimestamp is the Block's timestamp, seconds from epoch
BlkTimestamp

invalidBlockField // compile-time constant for number of fields
)

Expand Down
7 changes: 4 additions & 3 deletions data/transactions/logic/fields_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions data/transactions/logic/fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ func TestTxnFieldVersions(t *testing.T) {
asmError = "...txna opcode was introduced in ..."
txnaMode = true
}

// tack on a type check, and return a value (`int` gets compiled
// differently in different versions, so use `txn FirstValid` to get
// a positive integer)
switch fs.ftype.AVMType {
case avmUint64: // ensure the return type is uint64 by using !
text += "; !; pop; txn FirstValid"
case avmBytes: // ensure the return type is bytes by using len
text += "; len; pop; txn FirstValid"
case avmAny:
text += "; pop; txn FirstValid"
}

// check assembler fails if version before introduction
testLine(t, text, assemblerNoVersion, asmError)
for v := uint64(0); v < fs.version; v++ {
Expand All @@ -124,6 +137,18 @@ func TestTxnFieldVersions(t *testing.T) {

ops := testProg(t, text, AssemblerMaxVersion)

// check success in AssemblerMaxVersion, fs.version
// also ensures the field returns the right type
if !fs.effects {
txgroup[0].Txn.ApprovalProgram = []byte("approve") // not in standard sample txn
txgroup[0].Txn.ClearStateProgram = []byte("clear")
ep := defaultAppParamsWithVersion(AssemblerMaxVersion, txgroup...)
testAppBytes(t, ops.Program, ep)
opsv := testProg(t, text, fs.version)
ep = defaultAppParamsWithVersion(fs.version, txgroup...)
testAppBytes(t, opsv.Program, ep)
}

preVersion := fs.version - 1
ep := defaultSigParamsWithVersion(preVersion, txgroup...)

Expand Down Expand Up @@ -283,3 +308,36 @@ func TestAcctParamsFieldsVersions(t *testing.T) {

}
}

func TestBlockFieldsVersions(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

for _, field := range blockFieldSpecs {
text := fmt.Sprintf("txn FirstValid; int 1; - ; block %s;", field.field)
if field.ftype.AVMType == avmBytes {
text += "global ZeroAddress; concat; len" // use concat to prove we have bytes
} else {
text += "global ZeroAddress; len; +" // use + to prove we have an int
}

testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) {
v := ep.Proto.LogicSigVersion
if field.version > v {
// check assembler fails if version before introduction
testProg(t, text, v, exp(1, "...was introduced in..."))
ops := testProg(t, text, field.version) // assemble in the future
ops.Program[0] = byte(v) // but set version back to before intro
if v < randomnessVersion {
testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
} else {
testAppBytes(t, ops.Program, ep, "invalid block field")
}
} else {
testProg(t, text, v)
testApp(t, text, ep)
}
})

}
}
6 changes: 4 additions & 2 deletions data/transactions/logic/langspec_v10.json
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,8 @@
"CallerApplicationID",
"CallerApplicationAddress",
"AssetCreateMinBalance",
"AssetOptInMinBalance"
"AssetOptInMinBalance",
"GenesisHash"
],
"ArgEnumTypes": [
"uint64",
Expand All @@ -1215,7 +1216,8 @@
"uint64",
"address",
"uint64",
"uint64"
"uint64",
"[32]byte"
],
"DocCost": "1",
"Doc": "global field F",
Expand Down
6 changes: 6 additions & 0 deletions data/transactions/logic/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"math"
"math/rand"

"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/committee"
Expand Down Expand Up @@ -607,6 +608,11 @@ func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Addr
return basics.AppParams{}, basics.Address{}, fmt.Errorf("no app %d", appID)
}

// GenesisHash returns a phony genesis hash that can be tested against
func (l *Ledger) GenesisHash() crypto.Digest {
return crypto.Digest{0x03, 0x02, 0x03}
}

func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error {
fbr, ok := l.balances[from]
if !ok {
Expand Down
2 changes: 1 addition & 1 deletion data/transactions/logic/teal.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
},
{
"name": "variable.parameter.teal",
"match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b"
"match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b"
}
]
},
Expand Down
5 changes: 3 additions & 2 deletions ledger/eval/appcow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
Expand Down Expand Up @@ -99,8 +100,8 @@ func (ml *emptyLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, erro
return bookkeeping.BlockHeader{}, nil
}

func (ml *emptyLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, nil
func (ml *emptyLedger) GenesisHash() crypto.Digest {
return crypto.Digest{}
}

func (ml *emptyLedger) GetStateProofNextRound() basics.Round {
Expand Down
6 changes: 6 additions & 0 deletions ledger/eval/cow.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"sync"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
Expand Down Expand Up @@ -65,6 +66,7 @@ type roundCowParent interface {
getKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error)
kvGet(key string) ([]byte, bool, error)
GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error)
GenesisHash() crypto.Digest
}

// When adding new fields make sure to clear them in the roundCowState.recycle() as well to avoid dirty state
Expand Down Expand Up @@ -245,6 +247,10 @@ func (cb *roundCowState) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, erro
return cb.lookupParent.BlockHdr(r)
}

func (cb *roundCowState) GenesisHash() crypto.Digest {
return cb.lookupParent.GenesisHash()
}

func (cb *roundCowState) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) {
return cb.lookupParent.GetStateProofVerificationContext(stateProofLastAttestedRound)
}
Expand Down

0 comments on commit d7edd37

Please sign in to comment.