From 5c749334a1f417dc973f5019f20c7f66d7973cb6 Mon Sep 17 00:00:00 2001 From: Jack Smith Date: Thu, 19 Jan 2023 07:57:02 -0700 Subject: [PATCH 1/2] Bump version in buildnumber.dat --- buildnumber.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildnumber.dat b/buildnumber.dat index 0cfbf08886..00750edc07 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -2 +3 From aa42ea19afd8fcd4f8e4de343a0c38a02fadf5c5 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 19 Jan 2023 11:14:52 -0500 Subject: [PATCH 2/2] Empty box hotfix for relstable (#16) --- ledger/applications_test.go | 134 ++++++++++++++++++++++++++++++++++++ ledger/store/sql.go | 10 ++- 2 files changed, 141 insertions(+), 3 deletions(-) diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 67dbfd50be..1d27bb9ed1 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -18,12 +18,16 @@ package ledger import ( "encoding/hex" + "fmt" + "path/filepath" "testing" "time" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "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/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -1300,3 +1304,133 @@ int 1 err = l.appendUnvalidatedSignedTx(t, genesisInitState.Accounts, signedLsigPayment, transactions.ApplyData{}) a.NoError(err) } + +func TestAppEmptyBox(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(t) + source := `#pragma version 8 +txn ApplicationID +int 0 +== +bnz create_box +// otherwise delete the box +byte "boxname" +box_del +return + +create_box: +byte "boxname" +int 0 +box_create +return +` + + ops, err := logic.AssembleString(source) + a.NoError(err, ops.Errors) + a.Greater(len(ops.Program), 1) + program := ops.Program + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 1000000) + + creator, err := basics.UnmarshalChecksumAddress("3LN5DBFC2UTPD265LQDP3LMTLGZCQ5M3JV7XTVTGRH5CKSVNQVDFPN6FG4") + a.NoError(err) + a.Contains(genesisInitState.Accounts, creator) + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + dbPrefix := filepath.Join(t.TempDir(), dbName) + + cfg := config.GetDefaultLocal() + cfg.MaxAcctLookback = 2 + l1, err := OpenLedger(logging.Base(), dbPrefix, false, genesisInitState, cfg) + a.NoError(err) + defer l1.Close() + + genesisID := t.Name() + txHeader := transactions.Header{ + Sender: creator, + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: l1.Latest() + 1, + LastValid: l1.Latest() + 10, + GenesisID: genesisID, + GenesisHash: genesisInitState.GenesisHash, + } + + appIdx := basics.AppIndex(2) // second tnx => idx = 2 + + // fund app account + fundingPayment := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: txHeader, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: appIdx.Address(), + Amount: basics.MicroAlgos{Raw: 100*proto.MinBalance + proto.MinTxnFee}, + }, + } + err = l1.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, fundingPayment, transactions.ApplyData{}) + a.NoError(err) + + // create application + approvalProgram := program + clearStateProgram := []byte("\x08") // empty + appCreateFields := transactions.ApplicationCallTxnFields{ + ApprovalProgram: approvalProgram, + ClearStateProgram: clearStateProgram, + GlobalStateSchema: basics.StateSchema{NumByteSlice: 0}, + LocalStateSchema: basics.StateSchema{NumByteSlice: 0}, + Boxes: []transactions.BoxRef{{Name: []byte("boxname")}}, + } + appCreate := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: txHeader, + ApplicationCallTxnFields: appCreateFields, + } + err = l1.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: 2}) + a.NoError(err) + + // few empty blocks to reset deltas and flush + for i := 0; i < 10; i++ { + blk := makeNewEmptyBlock(t, l1, genesisID, genesisInitState.Accounts) + l1.AddBlock(blk, agreement.Certificate{}) + } + + app, err := l1.LookupApplication(l1.Latest(), creator, appIdx) + a.NoError(err) + a.Greater(len(app.AppParams.ApprovalProgram), 0) + + commitRound(10, 0, l1) + + // restart + l1.Close() + + l2, err := OpenLedger(logging.Base(), dbPrefix, false, genesisInitState, cfg) + a.NoError(err) + defer l2.Close() + + app, err = l2.LookupApplication(l2.Latest(), creator, appIdx) + a.NoError(err) + a.Greater(len(app.AppParams.ApprovalProgram), 0) + + txHeader = transactions.Header{ + Sender: creator, + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: l2.Latest() + 1, + LastValid: l2.Latest() + 10, + GenesisID: genesisID, + GenesisHash: genesisInitState.GenesisHash, + } + + appCallFields := transactions.ApplicationCallTxnFields{ + ApplicationID: appIdx, + Boxes: []transactions.BoxRef{{Name: []byte("boxname")}}, + } + appCall := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: txHeader, + ApplicationCallTxnFields: appCallFields, + } + err = l2.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, transactions.ApplyData{}) + a.NoError(err) + +} diff --git a/ledger/store/sql.go b/ledger/store/sql.go index ae8166faef..c7f8bb3095 100644 --- a/ledger/store/sql.go +++ b/ledger/store/sql.go @@ -80,7 +80,7 @@ func AccountsInitDbQueries(q db.Queryable) (*accountsDbQueries, error) { return nil, err } - qs.lookupKvPairStmt, err = q.Prepare("SELECT acctrounds.rnd, kvstore.value FROM acctrounds LEFT JOIN kvstore ON key = ? WHERE id='acctbase';") + qs.lookupKvPairStmt, err = q.Prepare("SELECT acctrounds.rnd, kvstore.key, kvstore.value FROM acctrounds LEFT JOIN kvstore ON key = ? WHERE id='acctbase';") if err != nil { return nil, err } @@ -253,9 +253,10 @@ func (qs *accountsDbQueries) ListCreatables(maxIdx basics.CreatableIndex, maxRes // LookupKeyValue returns the application boxed value associated with the key. func (qs *accountsDbQueries) LookupKeyValue(key string) (pv PersistedKVData, err error) { err = db.Retry(func() error { + var rawkey []byte var val []byte // Cast to []byte to avoid interpretation as character string, see note in upsertKvPair - err := qs.lookupKvPairStmt.QueryRow([]byte(key)).Scan(&pv.Round, &val) + err := qs.lookupKvPairStmt.QueryRow([]byte(key)).Scan(&pv.Round, &rawkey, &val) if err != nil { // this should never happen; it indicates that we don't have a current round in the acctrounds table. if err == sql.ErrNoRows { @@ -264,7 +265,10 @@ func (qs *accountsDbQueries) LookupKeyValue(key string) (pv PersistedKVData, err } return err } - if val != nil { // We got a non-null value, so it exists + if rawkey != nil { // We got a non-null key, so it exists + if val == nil { + val = []byte{} + } pv.Value = val return nil }