core, core/state, trie: EIP158, reprice & skip empty account write
This commit implements EIP158 part 1, 2, 3 & 4 1. If an account is empty it's no longer written to the trie. An empty account is defined as (balance=0, nonce=0, storage=0, code=0). 2. Delete an empty account if it's touched 3. An empty account is redefined as either non-existent or empty. 4. Zero value calls and zero value suicides no longer consume the 25k reation costs. params: moved core/config to params Signed-off-by: Jeffrey Wilcke <jeffrey@ethereum.org>
This commit is contained in:
@ -91,6 +91,11 @@ type StateObject struct {
|
||||
onDirty func(addr common.Address) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the account is considered empty.
|
||||
func (s *StateObject) empty() bool {
|
||||
return s.data.Nonce == 0 && s.data.Balance.BitLen() == 0 && bytes.Equal(s.data.CodeHash, emptyCodeHash)
|
||||
}
|
||||
|
||||
// Account is the Ethereum consensus representation of accounts.
|
||||
// These objects are stored in the main account trie.
|
||||
type Account struct {
|
||||
@ -221,8 +226,12 @@ func (self *StateObject) CommitTrie(db trie.Database, dbw trie.DatabaseWriter) e
|
||||
return err
|
||||
}
|
||||
|
||||
// AddBalance removes amount from c's balance.
|
||||
// It is used to add funds to the destination account of a transfer.
|
||||
func (c *StateObject) AddBalance(amount *big.Int) {
|
||||
if amount.Cmp(common.Big0) == 0 {
|
||||
// EIP158: We must check emptiness for the objects such that the account
|
||||
// clearing (0,0,0 objects) can take effect.
|
||||
if amount.Cmp(common.Big0) == 0 && !c.empty() {
|
||||
return
|
||||
}
|
||||
c.SetBalance(new(big.Int).Add(c.Balance(), amount))
|
||||
@ -232,6 +241,8 @@ func (c *StateObject) AddBalance(amount *big.Int) {
|
||||
}
|
||||
}
|
||||
|
||||
// SubBalance removes amount from c's balance.
|
||||
// It is used to remove funds from the origin account of a transfer.
|
||||
func (c *StateObject) SubBalance(amount *big.Int) {
|
||||
if amount.Cmp(common.Big0) == 0 {
|
||||
return
|
||||
|
@ -48,7 +48,7 @@ func (s *StateSuite) TestDump(c *checker.C) {
|
||||
// write some of them to the trie
|
||||
s.state.updateStateObject(obj1)
|
||||
s.state.updateStateObject(obj2)
|
||||
s.state.Commit()
|
||||
s.state.Commit(false)
|
||||
|
||||
// check that dump contains the state objects that are in trie
|
||||
got := string(s.state.Dump())
|
||||
@ -100,7 +100,7 @@ func TestNull(t *testing.T) {
|
||||
//value := common.FromHex("0x823140710bf13990e4500136726d8b55")
|
||||
var value common.Hash
|
||||
state.SetState(address, common.Hash{}, value)
|
||||
state.Commit()
|
||||
state.Commit(false)
|
||||
value = state.GetState(address, common.Hash{})
|
||||
if !common.EmptyHash(value) {
|
||||
t.Errorf("expected empty hash. got %x", value)
|
||||
@ -160,7 +160,7 @@ func TestSnapshot2(t *testing.T) {
|
||||
so0.deleted = false
|
||||
state.setStateObject(so0)
|
||||
|
||||
root, _ := state.Commit()
|
||||
root, _ := state.Commit(false)
|
||||
state.Reset(root)
|
||||
|
||||
// and one with deleted == true
|
||||
|
@ -213,6 +213,13 @@ func (self *StateDB) Exist(addr common.Address) bool {
|
||||
return self.GetStateObject(addr) != nil
|
||||
}
|
||||
|
||||
// Empty returns whether the state object is either non-existant
|
||||
// or empty according to the EIP161 specification (balance = nonce = code = 0)
|
||||
func (self *StateDB) Empty(addr common.Address) bool {
|
||||
so := self.GetStateObject(addr)
|
||||
return so == nil || so.empty()
|
||||
}
|
||||
|
||||
func (self *StateDB) GetAccount(addr common.Address) vm.Account {
|
||||
return self.GetStateObject(addr)
|
||||
}
|
||||
@ -516,10 +523,10 @@ func (self *StateDB) GetRefund() *big.Int {
|
||||
// IntermediateRoot computes the current root hash of the state trie.
|
||||
// It is called in between transactions to get the root hash that
|
||||
// goes into transaction receipts.
|
||||
func (s *StateDB) IntermediateRoot() common.Hash {
|
||||
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||
for addr, _ := range s.stateObjectsDirty {
|
||||
stateObject := s.stateObjects[addr]
|
||||
if stateObject.suicided {
|
||||
if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) {
|
||||
s.deleteStateObject(stateObject)
|
||||
} else {
|
||||
stateObject.updateRoot(s.db)
|
||||
@ -553,17 +560,17 @@ func (s *StateDB) DeleteSuicides() {
|
||||
}
|
||||
|
||||
// Commit commits all state changes to the database.
|
||||
func (s *StateDB) Commit() (root common.Hash, err error) {
|
||||
root, batch := s.CommitBatch()
|
||||
func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
|
||||
root, batch := s.CommitBatch(deleteEmptyObjects)
|
||||
return root, batch.Write()
|
||||
}
|
||||
|
||||
// CommitBatch commits all state changes to a write batch but does not
|
||||
// execute the batch. It is used to validate state changes against
|
||||
// the root hash stored in a block.
|
||||
func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) {
|
||||
func (s *StateDB) CommitBatch(deleteEmptyObjects bool) (root common.Hash, batch ethdb.Batch) {
|
||||
batch = s.db.NewBatch()
|
||||
root, _ = s.commit(batch)
|
||||
root, _ = s.commit(batch, deleteEmptyObjects)
|
||||
|
||||
glog.V(logger.Debug).Infof("Trie cache stats: %d misses, %d unloads", trie.CacheMisses(), trie.CacheUnloads())
|
||||
return root, batch
|
||||
@ -575,16 +582,18 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||
s.refund = new(big.Int)
|
||||
}
|
||||
|
||||
func (s *StateDB) commit(dbw trie.DatabaseWriter) (root common.Hash, err error) {
|
||||
func (s *StateDB) commit(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (root common.Hash, err error) {
|
||||
defer s.clearJournalAndRefund()
|
||||
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.stateObjects {
|
||||
if stateObject.suicided {
|
||||
_, isDirty := s.stateObjectsDirty[addr]
|
||||
switch {
|
||||
case stateObject.suicided || (isDirty && deleteEmptyObjects && stateObject.empty()):
|
||||
// If the object has been removed, don't bother syncing it
|
||||
// and just mark it for deletion in the trie.
|
||||
s.deleteStateObject(stateObject)
|
||||
} else if _, ok := s.stateObjectsDirty[addr]; ok {
|
||||
case isDirty:
|
||||
// Write any contract code associated with the state object
|
||||
if stateObject.code != nil && stateObject.dirtyCode {
|
||||
if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil {
|
||||
|
@ -51,7 +51,7 @@ func TestUpdateLeaks(t *testing.T) {
|
||||
if i%3 == 0 {
|
||||
state.SetCode(addr, []byte{i, i, i, i, i})
|
||||
}
|
||||
state.IntermediateRoot()
|
||||
state.IntermediateRoot(false)
|
||||
}
|
||||
// Ensure that no data was leaked into the database
|
||||
for _, key := range db.Keys() {
|
||||
@ -86,7 +86,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
modify(transState, common.Address{byte(i)}, i, 0)
|
||||
}
|
||||
// Write modifications to trie.
|
||||
transState.IntermediateRoot()
|
||||
transState.IntermediateRoot(false)
|
||||
|
||||
// Overwrite all the data with new values in the transient database.
|
||||
for i := byte(0); i < 255; i++ {
|
||||
@ -95,10 +95,10 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
}
|
||||
|
||||
// Commit and cross check the databases.
|
||||
if _, err := transState.Commit(); err != nil {
|
||||
if _, err := transState.Commit(false); err != nil {
|
||||
t.Fatalf("failed to commit transition state: %v", err)
|
||||
}
|
||||
if _, err := finalState.Commit(); err != nil {
|
||||
if _, err := finalState.Commit(false); err != nil {
|
||||
t.Fatalf("failed to commit final state: %v", err)
|
||||
}
|
||||
for _, key := range finalDb.Keys() {
|
||||
|
@ -60,7 +60,7 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
|
||||
state.updateStateObject(obj)
|
||||
accounts = append(accounts, acc)
|
||||
}
|
||||
root, _ := state.Commit()
|
||||
root, _ := state.Commit(false)
|
||||
|
||||
// Return the generated state
|
||||
return db, root, accounts
|
||||
|
Reference in New Issue
Block a user