Skip to content
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
10 changes: 1 addition & 9 deletions consensus/tests/engine_v1_tests/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/trie"
)

Expand Down Expand Up @@ -128,14 +127,7 @@ func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.S
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
storage := make(map[common.Hash]common.Hash)
f := func(key, val common.Hash) bool {
decode := []byte{}
trim := bytes.TrimLeft(val.Bytes(), "\x00")
err := rlp.DecodeBytes(trim, &decode)
if err != nil {
t.Fatalf("Failed while decode byte")
}
storage[key] = common.BytesToHash(decode)
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
storage[key] = val
return true
}
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
Expand Down
28 changes: 3 additions & 25 deletions consensus/tests/engine_v2_tests/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/trie"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -178,14 +177,7 @@ func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.S
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
storage := make(map[common.Hash]common.Hash)
f := func(key, val common.Hash) bool {
decode := []byte{}
trim := bytes.TrimLeft(val.Bytes(), "\x00")
err := rlp.DecodeBytes(trim, &decode)
if err != nil {
t.Fatalf("Failed while decode byte")
}
storage[key] = common.BytesToHash(decode)
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
storage[key] = val
return true
}
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
Expand Down Expand Up @@ -262,14 +254,7 @@ func getMultiCandidatesBackend(t *testing.T, chainConfig *params.ChainConfig, n
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
storage := make(map[common.Hash]common.Hash)
f := func(key, val common.Hash) bool {
decode := []byte{}
trim := bytes.TrimLeft(val.Bytes(), "\x00")
err := rlp.DecodeBytes(trim, &decode)
if err != nil {
t.Fatalf("Failed while decode byte")
}
storage[key] = common.BytesToHash(decode)
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
storage[key] = val
return true
}
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
Expand Down Expand Up @@ -347,14 +332,7 @@ func getProtectorObserverBackend(t *testing.T, chainConfig *params.ChainConfig)
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
storage := make(map[common.Hash]common.Hash)
f := func(key, val common.Hash) bool {
decode := []byte{}
trim := bytes.TrimLeft(val.Bytes(), "\x00")
err := rlp.DecodeBytes(trim, &decode)
if err != nil {
t.Fatalf("Failed while decode byte")
}
storage[key] = common.BytesToHash(decode)
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
storage[key] = val
return true
}
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
Expand Down
9 changes: 1 addition & 8 deletions contracts/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package validator

import (
"bytes"
"context"
"encoding/json"
"fmt"
Expand All @@ -36,7 +35,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
)

var (
Expand Down Expand Up @@ -300,12 +298,7 @@ func TestStatedbUtils(t *testing.T) {
code, _ := contractBackend.CodeAt(ctx, validatorAddress, nil)
storage := make(map[common.Hash]common.Hash)
f := func(key, val common.Hash) bool {
decode := []byte{}
trim := bytes.TrimLeft(val.Bytes(), "\x00")
rlp.DecodeBytes(trim, &decode)
storage[key] = common.BytesToHash(decode)
// t.Log("DecodeBytes", "value", val.String(), "decode", storage[key].String())
// t.Log("key", key.String(), "value", storage[key].String())
storage[key] = val
return true
}
contractBackend.ForEachStorageAt(ctx, validatorAddress, nil, f)
Expand Down
75 changes: 75 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1769,3 +1769,78 @@ func TestEIP3651(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}
}

// TestDeleteCreateRevert tests a weird state transition corner case that we hit
// while changing the internals of statedb. The workflow is that a contract is
// self destructed, then in a followup transaction (but same block) it's created
// again and the transaction reverted.
//
// The original statedb implementation flushed dirty objects to the tries after
// each transaction, so this works ok. The rework accumulated writes in memory
// first, but the journal wiped the entire state object on create-revert.
func TestDeleteCreateRevert(t *testing.T) {
var (
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
// Generate a canonical chain to act as the main dataset
engine = ethash.NewFaker()
db = rawdb.NewMemoryDatabase()

// A sender who makes transactions, has some funds
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(10000000000000000)
gspec = &Genesis{
Config: params.TestChainConfig,
Alloc: GenesisAlloc{
address: {Balance: funds},
// The address 0xAAAAA selfdestructs if called
aa: {
// Code needs to just selfdestruct
Code: []byte{byte(vm.PC), 0xFF},
Nonce: 1,
Balance: big.NewInt(0),
},
// The address 0xBBBB send 1 wei to 0xAAAA, then reverts
bb: {
Code: []byte{
byte(vm.PC), // [0]
byte(vm.DUP1), // [0,0]
byte(vm.DUP1), // [0,0,0]
byte(vm.DUP1), // [0,0,0,0]
byte(vm.PUSH1), 0x01, // [0,0,0,0,1] (value)
byte(vm.PUSH2), 0xaa, 0xaa, // [0,0,0,0,1, 0xaaaa]
byte(vm.GAS),
byte(vm.CALL),
byte(vm.REVERT),
},
Balance: big.NewInt(1),
},
},
}
genesis = gspec.MustCommit(db)
)

blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1})
// One transaction to AAAA
tx, _ := types.SignTx(types.NewTransaction(0, aa,
big.NewInt(0), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key)
b.AddTx(tx)
// One transaction to BBBB
tx, _ = types.SignTx(types.NewTransaction(1, bb,
big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key)
b.AddTx(tx)
})
// Import the canonical chain
diskdb := rawdb.NewMemoryDatabase()
gspec.MustCommit(diskdb)

chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}
}
112 changes: 71 additions & 41 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ type stateObject struct {
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded

cachedStorage Storage // Storage entry cache to avoid duplicate reads
dirtyStorage Storage // Storage entries that need to be flushed to disk
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
dirtyStorage Storage // Storage entries that need to be flushed to disk
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.

// Cache flags.
dirtyCode bool // true if the code was updated
Expand Down Expand Up @@ -123,14 +124,18 @@ func newObject(db *StateDB, address common.Address, data Account, onDirty func(a
if data.CodeHash == nil {
data.CodeHash = types.EmptyCodeHash.Bytes()
}
if data.Root == (common.Hash{}) {
data.Root = types.EmptyRootHash
}
return &stateObject{
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
data: data,
cachedStorage: make(Storage),
dirtyStorage: make(Storage),
onDirty: onDirty,
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
data: data,
originStorage: make(Storage),
pendingStorage: make(Storage),
dirtyStorage: make(Storage),
onDirty: onDirty,
}
}

Expand Down Expand Up @@ -179,55 +184,50 @@ func (s *stateObject) getTrie(db Database) Trie {
return s.trie
}

func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
// GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode)
if s.fakeStorage != nil {
return s.fakeStorage[key]
}
// Track the amount of time wasted on reading the storage trie
defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now())
value := common.Hash{}
// Load from DB in case it is missing.
enc, err := s.getTrie(db).TryGet(key[:])
if err != nil {
s.setError(err)
return common.Hash{}
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
s.setError(err)
}
value.SetBytes(content)
// If we have a dirty value for this state entry, return it
value, dirty := s.dirtyStorage[key]
if dirty {
return value
}
return value
// Otherwise return the entry's original value
return s.GetCommittedState(db, key)
}

func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode)
if s.fakeStorage != nil {
return s.fakeStorage[key]
}
value, exists := s.cachedStorage[key]
if exists {
// If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending {
return value
}
// Load from DB in case it is missing.
if value, cached := s.originStorage[key]; cached {
return value
}
// Track the amount of time wasted on reading the storage trie
defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now())
// Otherwise load the value from the database
enc, err := s.getTrie(db).TryGet(key[:])
if err != nil {
s.setError(err)
return common.Hash{}
}
var value common.Hash
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
s.setError(err)
}
value.SetBytes(content)
}
if (value != common.Hash{}) {
s.cachedStorage[key] = value
}
s.originStorage[key] = value
return value
}

Expand Down Expand Up @@ -271,7 +271,6 @@ func (s *stateObject) SetStorage(storage map[common.Hash]common.Hash) {
}

func (s *stateObject) setState(key, value common.Hash) {
s.cachedStorage[key] = value
s.dirtyStorage[key] = value

if s.onDirty != nil {
Expand All @@ -280,13 +279,36 @@ func (s *stateObject) setState(key, value common.Hash) {
}
}

// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise() {
for key, value := range s.dirtyStorage {
s.pendingStorage[key] = value
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
}
}

// updateTrie writes cached storage modifications into the object's storage trie.
// It will return nil if the trie has not been loaded and no changes have been made
func (s *stateObject) updateTrie(db Database) Trie {
// Make sure all dirty slots are finalized into the pending storage area
s.finalise()
if len(s.pendingStorage) == 0 {
return s.trie
}
// Track the amount of time wasted on updating the storage trie
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
// Insert all the pending updates into the trie
tr := s.getTrie(db)
for key, value := range s.dirtyStorage {
delete(s.dirtyStorage, key)
for key, value := range s.pendingStorage {
// Skip noop changes, persist actual changes
if value == s.originStorage[key] {
continue
}
s.originStorage[key] = value

if (value == common.Hash{}) {
s.setError(tr.TryDelete(key[:]))
continue
Expand All @@ -295,13 +317,18 @@ func (s *stateObject) updateTrie(db Database) Trie {
v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
s.setError(tr.TryUpdate(key[:], v))
}
if len(s.pendingStorage) > 0 {
s.pendingStorage = make(Storage)
}
return tr
}

// UpdateRoot sets the trie root to the current root hash of
func (s *stateObject) updateRoot(db Database) {
s.updateTrie(db)

// If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil {
return
}
// Track the amount of time wasted on hashing the storage trie
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())

Expand All @@ -311,7 +338,10 @@ func (s *stateObject) updateRoot(db Database) {
// CommitTrie the storage trie of the object to dwb.
// This updates the trie root.
func (s *stateObject) CommitTrie(db Database) error {
s.updateTrie(db)
// If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil {
return nil
}
if s.dbErr != nil {
return s.dbErr
}
Expand Down Expand Up @@ -372,7 +402,7 @@ func (s *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *
}
stateObject.code = s.code
stateObject.dirtyStorage = s.dirtyStorage.Copy()
stateObject.cachedStorage = s.dirtyStorage.Copy()
stateObject.originStorage = s.originStorage.Copy()
stateObject.selfDestructed = s.selfDestructed
stateObject.dirtyCode = s.dirtyCode
stateObject.deleted = s.deleted
Expand Down
Loading