Skip to content

Commit

Permalink
[wip] introduces worldStateJson struct
Browse files Browse the repository at this point in the history
* introduces the worldStateJson struct to properly export the necessary
fields for worldstate marshaling and unmarshaling
* test still fails though
  • Loading branch information
dylanlott committed Aug 1, 2023
1 parent 8263572 commit b5e5a4c
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 40 deletions.
81 changes: 51 additions & 30 deletions persistence/trees/atomic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package trees

import (
"encoding/hex"
"fmt"
"io"
"os"
"testing"
Expand Down Expand Up @@ -103,23 +104,50 @@ func TestTreeStore_SaveAndLoad(t *testing.T) {
t.Parallel()
t.Run("should save a backup in a directory", func(t *testing.T) {
ts := newTestTreeStore(t)
tmpdir := t.TempDir()
backupDir := t.TempDir()
// assert that the directory is empty before backup
ok, err := isEmpty(tmpdir)
ok, err := isEmpty(backupDir)
require.NoError(t, err)
require.True(t, ok)

// Trigger a backup
require.NoError(t, ts.Backup(tmpdir))
require.NoError(t, ts.Backup(backupDir))

// assert that the directory is not empty after Backup has returned
ok, err = isEmpty(tmpdir)
ok, err = isEmpty(backupDir)
require.NoError(t, err)
require.False(t, ok)

// assert that the worldstate.json file exists after a backup

// Open the directory
dir, err := os.Open(backupDir)
if err != nil {
fmt.Printf("Error opening directory: %s\n", err)
return
}
defer dir.Close()

// Read directory entries one by one
files, err := dir.Readdir(0) // 0 means read all directory entries
if err != nil {
fmt.Printf("Error reading directory entries: %s\n", err)
return
}
require.Equal(t, len(files), len(stateTreeNames)+1) // +1 to account for the worldstate file

// Now files is a slice of FileInfo objects representing the directory entries
// You can work with them as needed.
for _, file := range files {
if file.IsDir() {
fmt.Printf("Directory: %s\n", file.Name())
} else {
fmt.Printf("File: %s\n", file.Name())
}
}
})
t.Run("should load a backup and maintain TreeStore hash integrity", func(t *testing.T) {
ctrl := gomock.NewController(t)
tmpDir := t.TempDir()

mockTxIndexer := mock_types.NewMockTxIndexer(ctrl)
mockBus := mock_modules.NewMockBus(ctrl)
Expand All @@ -128,46 +156,39 @@ func TestTreeStore_SaveAndLoad(t *testing.T) {
mockBus.EXPECT().GetPersistenceModule().AnyTimes().Return(mockPersistenceMod)
mockPersistenceMod.EXPECT().GetTxIndexer().AnyTimes().Return(mockTxIndexer)

ts := &treeStore{
logger: logger.Global.CreateLoggerForModule(modules.TreeStoreSubmoduleName),
treeStoreDir: tmpDir,
}
require.NoError(t, ts.Start())
require.NotNil(t, ts.rootTree.tree)

for _, treeName := range stateTreeNames {
err := ts.merkleTrees[treeName].tree.Update([]byte("foo"), []byte("bar"))
require.NoError(t, err)
}
// create a new tree store and save it's initial hash
ts := newTestTreeStore(t)
hash1 := ts.getStateHash()

err := ts.Commit()
// make a temp directory for the backup and assert it's empty
backupDir := t.TempDir()
empty, err := isEmpty(backupDir)
require.NoError(t, err)
require.True(t, empty)

hash1 := ts.getStateHash()
require.NotEmpty(t, hash1)
// make a backup
err = ts.Backup(backupDir)
require.NoError(t, err)

w, err := ts.save()
// assert directory is not empty after backup
empty2, err := isEmpty(backupDir)
require.NoError(t, err)
require.NotNil(t, w)
require.NotNil(t, w.rootHash)
require.NotNil(t, w.merkleRoots)
require.False(t, empty2)

// Stop the first tree store so that it's databases are no longer used
// stop the first tree store so that it's databases are released
require.NoError(t, ts.Stop())

// declare a second TreeStore with no trees then load the first worldstate into it
ts2 := &treeStore{
logger: logger.Global.CreateLoggerForModule(modules.TreeStoreSubmoduleName),
treeStoreDir: tmpDir,
logger: logger.Global.CreateLoggerForModule(modules.TreeStoreSubmoduleName),
}

// Load sets a tree store to the provided worldstate
err = ts2.Load(tmpDir)
// call load with the backup directory
err = ts2.Load(backupDir)
require.NoError(t, err)

hash2 := ts2.getStateHash()

// Assert that hash is unchanged from save and load
hash2 := ts2.getStateHash()
require.Equal(t, hash1, hash2)
})
}
Expand Down
31 changes: 21 additions & 10 deletions persistence/trees/trees.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ type worldState struct {
merkleRoots map[string][]byte
}

// worldStateJson holds exported members for proper JSON marshaling and unmarshaling.
// It contains the root hash of the merkle roots as a byte slice and a map of the MerkleRoots
// where each key is the name of the file in the same directory that corresponds to the baderDB
// backup file for that tree. That tree's hash is the value of that object for checking the integrity
// of each file and tree.
type worldStateJson struct {
RootHash []byte
MerkleRoots map[string][]byte //
}

// GetTree returns the root hash and nodeStore for the matching tree stored in the TreeStore.
// This enables the caller to import the SMT without changing the one stored unless they call
// `Commit()` to write to the nodestore.
Expand Down Expand Up @@ -308,7 +318,8 @@ func (t *treeStore) getStateHash() string {
}
// Convert the array to a slice and return it
// REF: https://stackoverflow.com/questions/28886616/convert-array-to-slice-in-go
hexHash := hex.EncodeToString(t.rootTree.tree.Root())
root := t.rootTree.tree.Root()
hexHash := hex.EncodeToString(root)
t.logger.Info().Msgf("#️⃣ calculated state hash: %s", hexHash)
return hexHash
}
Expand Down Expand Up @@ -348,7 +359,7 @@ func (t *treeStore) Load(dir string) error {
}

// Hydrate a worldstate from the json object
var w *worldState
var w *worldStateJson
err = json.Unmarshal(data, &w)
if err != nil {
return err
Expand All @@ -364,12 +375,12 @@ func (t *treeStore) Load(dir string) error {
}
t.rootTree = &stateTree{
name: RootTreeName,
tree: smt.ImportSparseMerkleTree(nodeStore, smtTreeHasher, w.rootHash),
tree: smt.ImportSparseMerkleTree(nodeStore, smtTreeHasher, w.RootHash),
nodeStore: nodeStore,
}

// import merkle tree roots trees from worldState
for treeName, treeRootHash := range w.merkleRoots {
for treeName, treeRootHash := range w.MerkleRoots {
path := formattedTreePath(dir, treeName)
nodeStore, err := kvstore.NewKVStore(path)
if err != nil {
Expand Down Expand Up @@ -431,21 +442,21 @@ func (t *treeStore) Backup(backupDir string) error {
return err
}

w := &worldState{
rootHash: []byte(t.getStateHash()),
merkleRoots: make(map[string][]byte),
treeStoreDir: backupDir, // TODO IN THIS COMMIT make sure this is the proper formatting
w := &worldStateJson{
RootHash: []byte(t.getStateHash()),
MerkleRoots: make(map[string][]byte),
}

for _, st := range t.merkleTrees {
if err := st.nodeStore.Backup(formattedTreePath(backupDir, st.name)); err != nil {
t.logger.Err(err).Msgf("failed to backup %s tree: %+v", st.name, err)
return err
}
w.merkleRoots[st.name] = st.tree.Root()
w.MerkleRoots[st.name] = st.tree.Root()
}

err := writeFile(filepath.Join(backupDir, "worldstate.json"), w)
worldstatePath := filepath.Join(backupDir, "worldstate.json")
err := writeFile(worldstatePath, w)

Check failure on line 459 in persistence/trees/trees.go

View workflow job for this annotation

GitHub Actions / lint

ruleguard: consider using inline error check: if err := writeFile(worldstatePath, w); err != nil { $*_ } (gocritic)
if err != nil {
return err
}
Expand Down

0 comments on commit b5e5a4c

Please sign in to comment.