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:
Jeffrey Wilcke
2016-10-20 13:36:29 +02:00
parent 932d973e36
commit 445feaeef5
74 changed files with 729 additions and 573 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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() {

View File

@ -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