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

add state dump #341

Merged
merged 10 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pkg/trie/trie_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,35 @@ func (s *Trie) get(root, key []byte, batch [][]byte, iBatch, height int) ([]byte
return s.get(lnode, key, batch, 2*iBatch+1, height-1)
}

// GetKeys fetches all keys of shortcut.
func (s *Trie) GetKeys() [][]byte {
s.lock.RLock()
defer s.lock.RUnlock()
s.atomicUpdate = false

keys := make([][]byte, 0, 1024)
s.getKeys(s.Root, nil, 0, s.TrieHeight, &keys)
return keys
}

func (s *Trie) getKeys(root []byte, batch [][]byte, iBatch, height int, keys *[][]byte) {
if len(root) == 0 {
// the trie does not contain the key
return
}
// Fetch the children of the node
batch, iBatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, iBatch, batch)
if err != nil {
return
}
if isShortcut {
*keys = append(*keys, lnode[:HashLength])
return
}
s.getKeys(rnode, batch, 2*iBatch+2, height-1, keys)
s.getKeys(lnode, batch, 2*iBatch+1, height-1, keys)
}

// TrieRootExists returns true if the root exists in Database.
func (s *Trie) TrieRootExists(root []byte) bool {
s.db.lock.RLock()
Expand Down
99 changes: 99 additions & 0 deletions state/statedb/dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package statedb

import (
"encoding/json"

"github.com/aergoio/aergo/v2/internal/enc/base58"
"github.com/aergoio/aergo/v2/types"
)

type DumpAccount struct {
State *types.State
Code []byte
Storage map[types.AccountID][]byte
}

func (d DumpAccount) MarshalJSON() ([]byte, error) {
mapState := make(map[string]interface{})
mapState["nonce"] = d.State.Nonce
mapState["balance"] = d.State.GetBalanceBigInt().String()
mapState["codeHash"] = base58.Encode(d.State.CodeHash)
mapState["storageRoot"] = base58.Encode(d.State.StorageRoot)
mapState["sqlRecoveryPoint"] = d.State.SqlRecoveryPoint

mapStorage := make(map[string]string)
for k, v := range d.Storage {
mapStorage[k.String()] = base58.Encode(v)
}

return json.Marshal(map[string]interface{}{
"state": mapState,
"code": string(d.Code),
"storage": mapStorage,
})
}

type Dump struct {
Root []byte `json:"root"`
Accounts map[types.AccountID]DumpAccount `json:"accounts"`
}

func (d Dump) MarshalJSON() ([]byte, error) {
mapAccounts := make(map[string]DumpAccount)
for k, v := range d.Accounts {
mapAccounts[k.String()] = v
}

return json.Marshal(map[string]interface{}{
"root": base58.Encode(d.Root),
"accounts": mapAccounts,
})
}

func (sdb *StateDB) RawDump() (Dump, error) {
var err error

self := sdb.Clone()
dump := Dump{
Root: self.GetRoot(),
Accounts: make(map[types.AccountID]DumpAccount),
}

for _, key := range self.Trie.GetKeys() {
var st *types.State
var code []byte
storage := make(map[types.AccountID][]byte)

// load account state
aid := types.AccountID(types.ToHashID(key))
st, err = sdb.getState(aid)
if err != nil {
return dump, err
}
if len(st.GetCodeHash()) > 0 {
// load code
loadData(self.Store, st.GetCodeHash(), &code)

// load contract state
cs, err := OpenContractState(key, st, self)
if err != nil {
return dump, err
}

// load storage
for _, key := range cs.storage.Trie.GetKeys() {
data, _ := cs.getInitialData(key)
aid := types.AccountID(types.ToHashID(key))
storage[aid] = data
}
}

dump.Accounts[aid] = DumpAccount{
State: st,
Code: code,
Storage: storage,
}
}

return dump, nil
}
143 changes: 143 additions & 0 deletions state/statedb/dump_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package statedb

import (
"testing"

"github.com/aergoio/aergo/v2/internal/common"
"github.com/aergoio/aergo/v2/types"
"github.com/stretchr/testify/require"
)

func TestDumpAccount(t *testing.T) {
initTest(t)
defer deinitTest()

// set account state
for _, v := range testStates {
err := stateDB.PutState(testAccount, v)
require.NoError(t, err, "failed to put state")
}
err := stateDB.Update()
require.NoError(t, err, "failed to update")
err = stateDB.Commit()
require.NoError(t, err, "failed to commit")

dump, err := stateDB.Dump()
require.NoError(t, err)

require.Equal(t, string(dump), `{
"accounts": {
"9RhQjznbYXqMQG1GmuYSsvoCYe5bnCPZCTnT6ZvohkxN": {
"code": "",
"state": {
"balance": "500",
"codeHash": "",
"nonce": 5,
"sqlRecoveryPoint": 0,
"storageRoot": ""
},
"storage": {}
}
},
"root": "G1GECbeFFqSB4WhWCFF8b6tXfc8NCpa2XMqRmgbv7gDD"
}`)
}

func TestDumpContract(t *testing.T) {
initTest(t)
defer deinitTest()

code := "testcode"
codeHash := common.Hasher([]byte(code))
// save code
err := saveData(store, codeHash, []byte(code))
require.NoError(t, err, "failed to save code")
// set contract state
err = stateDB.PutState(testAccount, &types.State{
Balance: types.NewAmount(1, types.Aergo).Bytes(),
Nonce: 1,
CodeHash: common.Hasher([]byte(code)),
})
require.NoError(t, err, "failed to put state")

err = stateDB.Update()
require.NoError(t, err, "failed to update")
err = stateDB.Commit()
require.NoError(t, err, "failed to commit")

dump, err := stateDB.Dump()
require.NoError(t, err)

require.Equal(t, string(dump), `{
"accounts": {
"9RhQjznbYXqMQG1GmuYSsvoCYe5bnCPZCTnT6ZvohkxN": {
"code": "testcode",
"state": {
"balance": "1000000000000000000",
"codeHash": "6GBoUd26XJnkj6wGs1L6fLg8jhuVXSTVUNWzoXsjeHoh",
"nonce": 1,
"sqlRecoveryPoint": 0,
"storageRoot": ""
},
"storage": {}
}
},
"root": "9NfkNYKP6KKZbQ2S3CDkeWqWMfxv2zfNgETCPoqWkDmP"
}`)
}

func TestDumpStorage(t *testing.T) {
initTest(t)
defer deinitTest()

code := "testcode"
codeHash := common.Hasher([]byte(code))
// set code
err := saveData(store, codeHash, []byte(code))
require.NoError(t, err, "failed to save code")

// set contract state
err = stateDB.PutState(testAccount, &types.State{
Balance: types.NewAmount(1, types.Aergo).Bytes(),
Nonce: 1,
CodeHash: common.Hasher([]byte(code)),
})
require.NoError(t, err, "failed to put state")

// set contract storage
scs, err := OpenContractStateAccount([]byte("test_address"), stateDB)
require.NoError(t, err, "failed to open contract state account")
scs.SetData([]byte("test_storage1"), []byte("test_value1"))
scs.SetData([]byte("test_storage2"), []byte("test_value2"))
scs.SetData([]byte("test_storage3"), []byte("test_value3"))
StageContractState(scs, stateDB)

err = stateDB.Update()
require.NoError(t, err, "failed to update")
err = stateDB.Commit()
require.NoError(t, err, "failed to commit")

dump, err := stateDB.Dump()
require.NoError(t, err)

require.Equal(t, string(dump), `{
"accounts": {
"9RhQjznbYXqMQG1GmuYSsvoCYe5bnCPZCTnT6ZvohkxN": {
"code": "testcode",
"state": {
"balance": "1000000000000000000",
"codeHash": "6GBoUd26XJnkj6wGs1L6fLg8jhuVXSTVUNWzoXsjeHoh",
"nonce": 1,
"sqlRecoveryPoint": 0,
"storageRoot": "9SVveGGrFXJtoVFFiGpWZ1TmHmKLPnqoYmS7AGxzxgdL"
},
"storage": {
"BVsyGJb6L5qLr8EPcM78Wd5NgZz3cjC1jM2FxpmZFrBm": "Vs5LyU62cV3qLve",
"ByeMAs5g3t233iEGkEzMYoN4UrnaJPJ6TGNdY2vs9MBg": "Vs5LyU62cV3qLvd",
"q6MAgzMsY2iJcukzs5x7M9WFmN4bT9AiqzviD1DcSZX": "Vs5LyU62cV3qLvc"
}
}
},
"root": "9bQx52KdKfMkVdakKEWQLBscgkMiFwd2Zx2hr7u1GYam"
}`)
}
9 changes: 9 additions & 0 deletions state/statedb/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package statedb

import (
"bytes"
"encoding/json"
"errors"
"math/big"
"sync"
Expand Down Expand Up @@ -425,3 +426,11 @@ func (states *StateDB) IsLegacyTrieKey() bool {
}
return false
}

func (sdb *StateDB) Dump() ([]byte, error) {
dump, err := sdb.RawDump()
if err != nil {
return nil, err
}
return json.MarshalIndent(dump, "", "\t")
}