Skip to content

Commit 7f7082d

Browse files
authored
Merge pull request #2045 from onetechnical/onetechnical/relbeta2.5.4
go-algorand 2.5.4-beta
2 parents 656f3ad + de8da5e commit 7f7082d

File tree

11 files changed

+376
-28
lines changed

11 files changed

+376
-28
lines changed

buildnumber.dat

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
4

crypto/merkletrie/cache.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ func (mtc *merkleTrieCache) initialize(mt *Trie, committer Committer, memoryConf
109109
mtc.targetPageFillFactor = memoryConfig.PageFillFactor
110110
mtc.maxChildrenPagesThreshold = memoryConfig.MaxChildrenPagesThreshold
111111
if mt.nextNodeID != storedNodeIdentifierBase {
112-
// if the next node is going to be on a new page, no need to reload the last page.
113-
if (int64(mtc.mt.nextNodeID) / mtc.nodesPerPage) == (int64(mtc.mt.nextNodeID-1) / mtc.nodesPerPage) {
112+
// If the next node would reside on a page that already has a few entries in it, make sure to mark it for late loading.
113+
// Otherwise, the next node is going to be the first node on this page, we don't need to reload that page ( since it doesn't exist! ).
114+
if (int64(mtc.mt.nextNodeID) % mtc.nodesPerPage) > 0 {
114115
mtc.deferedPageLoad = uint64(mtc.mt.nextNodeID) / uint64(mtc.nodesPerPage)
115116
}
116117
}
@@ -262,7 +263,7 @@ func (mtc *merkleTrieCache) loadPage(page uint64) (err error) {
262263
}
263264

264265
// if we've just loaded a deferred page, no need to reload it during the commit.
265-
if mtc.deferedPageLoad != page {
266+
if mtc.deferedPageLoad == page {
266267
mtc.deferedPageLoad = storedNodeIdentifierNull
267268
}
268269
return

crypto/merkletrie/cache_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -454,3 +454,36 @@ func TestCachePagedOutTip(t *testing.T) {
454454
page = uint64(mt1.root) / uint64(memConfig.NodesCountPerPage)
455455
require.NotNil(t, mt1.cache.pageToNIDsPtr[page])
456456
}
457+
458+
// TestCacheLoadingDeferedPage verifies that the loadPage
459+
// method correcly resets the mtc.deferedPageLoad on the correct page.
460+
func TestCacheLoadingDeferedPage(t *testing.T) {
461+
var memoryCommitter1 InMemoryCommitter
462+
mt1, _ := MakeTrie(&memoryCommitter1, defaultTestMemoryConfig)
463+
// create 100000 hashes.
464+
leafsCount := 100000
465+
hashes := make([]crypto.Digest, leafsCount)
466+
for i := 0; i < len(hashes); i++ {
467+
hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)})
468+
}
469+
470+
for i := 0; i < len(hashes); i++ {
471+
mt1.Add(hashes[i][:])
472+
}
473+
_, err := mt1.Commit()
474+
require.NoError(t, err)
475+
476+
// verify that the cache doesn't reset the mtc.deferedPageLoad on loading a non-defered page.
477+
dupMem := memoryCommitter1.Duplicate()
478+
mt2, _ := MakeTrie(dupMem, defaultTestMemoryConfig)
479+
lastPage := int64(mt2.nextNodeID) / defaultTestMemoryConfig.NodesCountPerPage
480+
require.Equal(t, uint64(lastPage), mt2.cache.deferedPageLoad)
481+
err = mt2.cache.loadPage(uint64(lastPage - 1))
482+
require.NoError(t, err)
483+
require.Equal(t, uint64(lastPage), mt2.cache.deferedPageLoad)
484+
485+
// verify that the cache does reset the mtc.deferedPageLoad on loading a defered page.
486+
err = mt2.cache.loadPage(uint64(lastPage))
487+
require.NoError(t, err)
488+
require.Equal(t, uint64(0), mt2.cache.deferedPageLoad)
489+
}

ledger/accountdb.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,7 @@ func makeMerkleCommitter(tx *sql.Tx, staging bool) (mc *merkleCommitter, err err
13411341
return mc, nil
13421342
}
13431343

1344-
// StorePage stores a single page in an in-memory persistence.
1344+
// StorePage is the merkletrie.Committer interface implementation, stores a single page in a sqllite database table.
13451345
func (mc *merkleCommitter) StorePage(page uint64, content []byte) error {
13461346
if len(content) == 0 {
13471347
_, err := mc.deleteStmt.Exec(page)
@@ -1351,7 +1351,7 @@ func (mc *merkleCommitter) StorePage(page uint64, content []byte) error {
13511351
return err
13521352
}
13531353

1354-
// LoadPage load a single page from an in-memory persistence.
1354+
// LoadPage is the merkletrie.Committer interface implementation, load a single page from a sqllite database table.
13551355
func (mc *merkleCommitter) LoadPage(page uint64) (content []byte, err error) {
13561356
err = mc.selectStmt.QueryRow(page).Scan(&content)
13571357
if err == sql.ErrNoRows {

ledger/appcow.go

+13-8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/algorand/go-algorand/data/transactions"
2626
"github.com/algorand/go-algorand/data/transactions/logic"
2727
"github.com/algorand/go-algorand/ledger/apply"
28+
"github.com/algorand/go-algorand/ledger/ledgercore"
2829
"github.com/algorand/go-algorand/protocol"
2930
)
3031

@@ -419,7 +420,7 @@ func (cb *roundCowState) StatefulEval(params logic.EvalParams, aidx basics.AppIn
419420
// Eval the program
420421
pass, err = logic.EvalStateful(program, params)
421422
if err != nil {
422-
return false, basics.EvalDelta{}, err
423+
return false, basics.EvalDelta{}, ledgercore.LogicEvalError{Err: err}
423424
}
424425

425426
// If program passed, build our eval delta, and commit to state changes
@@ -566,16 +567,18 @@ func applyStorageDelta(data basics.AccountData, aapp storagePtr, store *storageD
566567
case deallocAction:
567568
delete(owned, aapp.aidx)
568569
case allocAction, remainAllocAction:
569-
// TODO verify this assertion
570570
// note: these should always exist because they were
571-
// at least preceded by a call to PutWithCreatable?
571+
// at least preceded by a call to PutWithCreatable
572572
params, ok := owned[aapp.aidx]
573573
if !ok {
574574
return basics.AccountData{}, fmt.Errorf("could not find existing params for %v", aapp.aidx)
575575
}
576576
params = params.Clone()
577-
if store.action == allocAction {
578-
// TODO does this ever accidentally clobber?
577+
if (store.action == allocAction && len(store.kvCow) > 0) ||
578+
(store.action == remainAllocAction && params.GlobalState == nil) {
579+
// allocate KeyValue for
580+
// 1) app creation and global write in the same app call
581+
// 2) global state writing into empty global state
579582
params.GlobalState = make(basics.TealKeyValue)
580583
}
581584
// note: if this is an allocAction, there will be no
@@ -602,16 +605,18 @@ func applyStorageDelta(data basics.AccountData, aapp storagePtr, store *storageD
602605
case deallocAction:
603606
delete(owned, aapp.aidx)
604607
case allocAction, remainAllocAction:
605-
// TODO verify this assertion
606608
// note: these should always exist because they were
607609
// at least preceded by a call to Put?
608610
states, ok := owned[aapp.aidx]
609611
if !ok {
610612
return basics.AccountData{}, fmt.Errorf("could not find existing states for %v", aapp.aidx)
611613
}
612614
states = states.Clone()
613-
if store.action == allocAction {
614-
// TODO does this ever accidentally clobber?
615+
if (store.action == allocAction && len(store.kvCow) > 0) ||
616+
(store.action == remainAllocAction && states.KeyValue == nil) {
617+
// allocate KeyValue for
618+
// 1) opting in and local state write in the same app call
619+
// 2) local state writing into empty local state (opted in)
615620
states.KeyValue = make(basics.TealKeyValue)
616621
}
617622
// note: if this is an allocAction, there will be no

ledger/applications_test.go

+235
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ package ledger
1818

1919
import (
2020
"crypto/rand"
21+
"encoding/hex"
2122
"fmt"
2223
"testing"
2324

2425
"github.com/stretchr/testify/require"
2526

27+
"github.com/algorand/go-algorand/config"
2628
"github.com/algorand/go-algorand/crypto"
2729
"github.com/algorand/go-algorand/data/basics"
2830
"github.com/algorand/go-algorand/data/transactions"
31+
"github.com/algorand/go-algorand/data/transactions/logic"
32+
"github.com/algorand/go-algorand/logging"
33+
"github.com/algorand/go-algorand/protocol"
2934
)
3035

3136
func getRandomAddress(a *require.Assertions) basics.Address {
@@ -332,3 +337,233 @@ func TestLogicLedgerDelKey(t *testing.T) {
332337
err = l.DelLocal(addr1, "lkey")
333338
a.NoError(err)
334339
}
340+
341+
// test ensures that
342+
// 1) app's GlobalState and local state's KeyValue are stored in the same way
343+
// before and after application code refactoring
344+
// 2) writing into empty (opted-in) local state's KeyValue works after reloading
345+
// Hardcoded values are from commit 9a0b439 (pre app refactor commit)
346+
func TestAppAccountDataStorage(t *testing.T) {
347+
a := require.New(t)
348+
source := `#pragma version 2
349+
// do not write local key on opt in or on app create
350+
txn ApplicationID
351+
int 0
352+
==
353+
bnz success
354+
txn OnCompletion
355+
int NoOp
356+
==
357+
bnz writetostate
358+
txn OnCompletion
359+
int OptIn
360+
==
361+
bnz checkargs
362+
int 0
363+
return
364+
checkargs:
365+
// if no args the success
366+
// otherwise write data
367+
txn NumAppArgs
368+
int 0
369+
==
370+
bnz success
371+
// write local or global key depending on arg1
372+
writetostate:
373+
txna ApplicationArgs 0
374+
byte "local"
375+
==
376+
bnz writelocal
377+
txna ApplicationArgs 0
378+
byte "global"
379+
==
380+
bnz writeglobal
381+
int 0
382+
return
383+
writelocal:
384+
int 0
385+
byte "lk"
386+
byte "local"
387+
app_local_put
388+
b success
389+
writeglobal:
390+
byte "gk"
391+
byte "global"
392+
app_global_put
393+
success:
394+
int 1
395+
return`
396+
397+
ops, err := logic.AssembleString(source)
398+
a.NoError(err)
399+
a.Greater(len(ops.Program), 1)
400+
program := ops.Program
401+
402+
proto := config.Consensus[protocol.ConsensusCurrentVersion]
403+
genesisInitState, initKeys := testGenerateInitState(t, protocol.ConsensusCurrentVersion)
404+
405+
creator, err := basics.UnmarshalChecksumAddress("3LN5DBFC2UTPD265LQDP3LMTLGZCQ5M3JV7XTVTGRH5CKSVNQVDFPN6FG4")
406+
a.NoError(err)
407+
userOptin, err := basics.UnmarshalChecksumAddress("6S6UMUQ4462XRGNON5GKBHW55RUJGJ5INIRDFVFD6KSPHGWGRKPC6RK2O4")
408+
a.NoError(err)
409+
userLocal, err := basics.UnmarshalChecksumAddress("UL5C6SRVLOROSB5FGAE6TY34VXPXVR7GNIELUB3DD5KTA4VT6JGOZ6WFAY")
410+
a.NoError(err)
411+
userLocal2, err := basics.UnmarshalChecksumAddress("XNOGOJECWDOMVENCDJHNMOYVV7PIVIJXRWTSZUA3GSKYTVXH3VVGOXP7CU")
412+
a.NoError(err)
413+
414+
a.Contains(genesisInitState.Accounts, creator)
415+
a.Contains(genesisInitState.Accounts, userOptin)
416+
a.Contains(genesisInitState.Accounts, userLocal)
417+
a.Contains(genesisInitState.Accounts, userLocal2)
418+
419+
expectedCreator, err := hex.DecodeString("84a4616c676fce009d2290a461707070810184a6617070726f76c45602200200012604056c6f63616c06676c6f62616c026c6b02676b3118221240003331192212400010311923124000022243311b221240001c361a00281240000a361a0029124000092243222a28664200032b29672343a6636c65617270c40102a46773636881a36e627304a46c73636881a36e627301a36f6e6c01a47473636881a36e627304")
420+
a.NoError(err)
421+
expectedUserOptIn, err := hex.DecodeString("84a4616c676fce00a02fd0a46170706c810181a46873636881a36e627301a36f6e6c01a47473636881a36e627301")
422+
a.NoError(err)
423+
expectedUserLocal, err := hex.DecodeString("84a4616c676fce00a33540a46170706c810182a46873636881a36e627301a3746b7681a26c6b82a27462a56c6f63616ca2747401a36f6e6c01a47473636881a36e627301")
424+
a.NoError(err)
425+
426+
cfg := config.GetDefaultLocal()
427+
l, err := OpenLedger(logging.Base(), "TestAppAccountData", true, genesisInitState, cfg)
428+
a.NoError(err)
429+
defer l.Close()
430+
431+
txHeader := transactions.Header{
432+
Sender: creator,
433+
Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2},
434+
FirstValid: l.Latest() + 1,
435+
LastValid: l.Latest() + 10,
436+
GenesisID: t.Name(),
437+
GenesisHash: genesisInitState.GenesisHash,
438+
}
439+
440+
// create application
441+
approvalProgram := program
442+
clearStateProgram := []byte("\x02") // empty
443+
appCreateFields := transactions.ApplicationCallTxnFields{
444+
ApprovalProgram: approvalProgram,
445+
ClearStateProgram: clearStateProgram,
446+
GlobalStateSchema: basics.StateSchema{NumByteSlice: 4},
447+
LocalStateSchema: basics.StateSchema{NumByteSlice: 1},
448+
}
449+
appCreate := transactions.Transaction{
450+
Type: protocol.ApplicationCallTx,
451+
Header: txHeader,
452+
ApplicationCallTxnFields: appCreateFields,
453+
}
454+
err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{})
455+
a.NoError(err)
456+
457+
appIdx := basics.AppIndex(1) // first tnx => idx = 1
458+
459+
// opt-in, do no write
460+
txHeader.Sender = userOptin
461+
appCallFields := transactions.ApplicationCallTxnFields{
462+
OnCompletion: transactions.OptInOC,
463+
ApplicationID: appIdx,
464+
}
465+
appCall := transactions.Transaction{
466+
Type: protocol.ApplicationCallTx,
467+
Header: txHeader,
468+
ApplicationCallTxnFields: appCallFields,
469+
}
470+
err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, transactions.ApplyData{})
471+
a.NoError(err)
472+
473+
// opt-in + write
474+
txHeader.Sender = userLocal
475+
appCall = transactions.Transaction{
476+
Type: protocol.ApplicationCallTx,
477+
Header: txHeader,
478+
ApplicationCallTxnFields: appCallFields,
479+
}
480+
err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, transactions.ApplyData{})
481+
a.NoError(err)
482+
483+
// save data into DB and write into local state
484+
l.accts.accountsWriting.Add(1)
485+
l.accts.commitRound(3, 0, 0)
486+
l.reloadLedger()
487+
488+
appCallFields = transactions.ApplicationCallTxnFields{
489+
OnCompletion: 0,
490+
ApplicationID: appIdx,
491+
ApplicationArgs: [][]byte{[]byte("local")},
492+
}
493+
appCall = transactions.Transaction{
494+
Type: protocol.ApplicationCallTx,
495+
Header: txHeader,
496+
ApplicationCallTxnFields: appCallFields,
497+
}
498+
err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall,
499+
transactions.ApplyData{EvalDelta: basics.EvalDelta{
500+
LocalDeltas: map[uint64]basics.StateDelta{0: {"lk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "local"}}}},
501+
})
502+
a.NoError(err)
503+
504+
// save data into DB
505+
l.accts.accountsWriting.Add(1)
506+
l.accts.commitRound(1, 3, 0)
507+
l.reloadLedger()
508+
509+
// dump accounts
510+
var rowid int64
511+
var dbRound basics.Round
512+
var buf []byte
513+
err = l.accts.accountsq.lookupStmt.QueryRow(creator[:]).Scan(&rowid, &dbRound, &buf)
514+
a.NoError(err)
515+
a.Equal(expectedCreator, buf)
516+
517+
err = l.accts.accountsq.lookupStmt.QueryRow(userOptin[:]).Scan(&rowid, &dbRound, &buf)
518+
a.NoError(err)
519+
a.Equal(expectedUserOptIn, buf)
520+
pad, err := l.accts.accountsq.lookup(userOptin)
521+
a.Nil(pad.accountData.AppLocalStates[appIdx].KeyValue)
522+
ad, err := l.Lookup(dbRound, userOptin)
523+
a.Nil(ad.AppLocalStates[appIdx].KeyValue)
524+
525+
err = l.accts.accountsq.lookupStmt.QueryRow(userLocal[:]).Scan(&rowid, &dbRound, &buf)
526+
a.NoError(err)
527+
a.Equal(expectedUserLocal, buf)
528+
529+
ad, err = l.Lookup(dbRound, userLocal)
530+
a.NoError(err)
531+
a.Equal("local", ad.AppLocalStates[appIdx].KeyValue["lk"].Bytes)
532+
533+
// ensure writing into empty global state works as well
534+
l.reloadLedger()
535+
txHeader.Sender = creator
536+
appCallFields = transactions.ApplicationCallTxnFields{
537+
OnCompletion: 0,
538+
ApplicationID: appIdx,
539+
ApplicationArgs: [][]byte{[]byte("global")},
540+
}
541+
appCall = transactions.Transaction{
542+
Type: protocol.ApplicationCallTx,
543+
Header: txHeader,
544+
ApplicationCallTxnFields: appCallFields,
545+
}
546+
err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall,
547+
transactions.ApplyData{EvalDelta: basics.EvalDelta{
548+
GlobalDelta: basics.StateDelta{"gk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "global"}}},
549+
})
550+
a.NoError(err)
551+
552+
// opt-in + write by during opt-in
553+
txHeader.Sender = userLocal2
554+
appCallFields = transactions.ApplicationCallTxnFields{
555+
OnCompletion: transactions.OptInOC,
556+
ApplicationID: appIdx,
557+
ApplicationArgs: [][]byte{[]byte("local")},
558+
}
559+
appCall = transactions.Transaction{
560+
Type: protocol.ApplicationCallTx,
561+
Header: txHeader,
562+
ApplicationCallTxnFields: appCallFields,
563+
}
564+
err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall,
565+
transactions.ApplyData{EvalDelta: basics.EvalDelta{
566+
LocalDeltas: map[uint64]basics.StateDelta{0: {"lk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "local"}}}},
567+
})
568+
a.NoError(err)
569+
}

0 commit comments

Comments
 (0)