go-ethereum/core/state/state.go
Jeffrey Wilcke 931c6a4936 core/state: improved state API and copying mechanism
The old state copy mechanism made deep copy for every object in state,
most of them not required by the new state and therefor wasting a ton of
resources. The copy of a state can be made using the `state.Fork`
function, which will allocate a new State and return it. This new object
holds a refernce to it's parent which can be used to query for any
information not currently available. When an object can not be found it
will recusively call the parent for the object until it reached the root
state and checks the state trie. Objects are now copied on demand and
will never be copied during the `state.Fork` function call.

State can also be flattened in to one single object, merging all the
state from root to current, leaving a single state with all recent
changes. State can be flattened using `state.Flatten`

In addition the following methods have changed to functions instead:

* state.Commit
* state.BatchCommit
* state.IntermediateRoot
2016-09-23 01:18:59 +02:00

394 lines
9.4 KiB
Go

package state
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
var StartingNonce uint64 // StartingNonce determines the nonce used when a new account is initialised.
// State keeps a Log of changes that occured during the sessions such as state object changes, Refunds and Logs. It also keeps a reference to it's parent state to retrieve data if anything is missing from this snapshot. This will allow us to copy on demand.
type State struct {
Db ethdb.Database
Trie *trie.SecureTrie
parent *State
StateObjects map[common.Address]*StateObject
refund *big.Int
logIdx uint
logs []*vm.Log
interInfo intermediateInfo
MarkedTransition bool // marks the transition between transactions
}
type intermediateInfo struct {
txHash, blockHash common.Hash
txIdx uint
}
// Create a new state from a given trie
func New(root common.Hash, db ethdb.Database) (*State, error) {
tr, err := trie.NewSecure(root, db)
if err != nil {
return nil, err
}
return &State{
Db: db,
Trie: tr,
StateObjects: make(map[common.Address]*StateObject),
refund: new(big.Int),
}, nil
}
func (s *State) PrepareIntermediate(txHash, blockHash common.Hash, txIdx int) {
if s.parent != nil {
s.parent.MarkedTransition = true
}
s.interInfo = intermediateInfo{txHash: txHash, blockHash: blockHash, txIdx: uint(txIdx)}
s.refund = new(big.Int)
}
// Flatten flattens one's own state
func (s *State) Flatten() {
*s = *Flatten(s)
}
// Read fetches the state object with the given address by first checking its own cache of state objects. If that didn't result in an object it will attempt to check it's line of ancestors.
func (s *State) Read(address common.Address) (object *StateObject, inCache bool) {
stateObject := s.StateObjects[address]
if stateObject == nil && s.parent != nil {
stateObject, _ = s.parent.Read(address)
} else if stateObject != nil {
inCache = true
}
if stateObject != nil {
if stateObject.deleted {
return nil, false
}
return stateObject, inCache
}
data := s.Trie.Get(address[:])
if len(data) == 0 {
return nil, false
}
var err error
stateObject, err = DecodeObject(address, s.Db, data)
if err != nil {
glog.Errorf("can't decode object at %x: %v", address[:], err)
return nil, false
}
return stateObject, false
}
func (s *State) GetOrNewStateObject(address common.Address) *StateObject {
stateObject := s.GetStateObject(address)
if stateObject == nil || stateObject.deleted {
stateObject = NewStateObject(address, s.Db)
stateObject.SetNonce(StartingNonce)
s.StateObjects[address] = stateObject
}
return stateObject
}
func (s *State) GetStateObject(address common.Address) *StateObject {
account, inCache := s.Read(address)
if account != nil {
if !inCache {
s.StateObjects[address] = account.Copy()
}
return s.StateObjects[address]
}
return nil
}
func (s *State) CreateStateObject(address common.Address) *StateObject {
// Get previous (if any)
so := s.GetStateObject(address)
// Create a new one
stateObject := NewStateObject(address, s.Db)
stateObject.SetNonce(StartingNonce)
s.StateObjects[address] = stateObject
// If it existed set the balance to the new account
if so != nil {
stateObject.balance = so.balance
}
return stateObject
}
func (s *State) AddBalance(address common.Address, amount *big.Int) {
stateObject := s.GetOrNewStateObject(address)
if stateObject != nil {
stateObject.AddBalance(amount)
}
}
func (s *State) GetBalance(address common.Address) *big.Int {
stateObject := s.GetStateObject(address)
if stateObject != nil {
return stateObject.balance
}
return common.Big0
}
func (s *State) GetNonce(address common.Address) uint64 {
stateObject := s.GetStateObject(address)
if stateObject != nil {
return stateObject.nonce
}
return StartingNonce
}
func (s *State) SetNonce(address common.Address, nonce uint64) {
stateObject := s.GetOrNewStateObject(address)
if stateObject != nil {
stateObject.SetNonce(nonce)
}
}
func (s *State) GetCode(address common.Address) []byte {
stateObject := s.GetStateObject(address)
if stateObject != nil {
return stateObject.code
}
return nil
}
func (s *State) SetCode(address common.Address, code []byte) {
stateObject := s.GetOrNewStateObject(address)
if stateObject != nil {
stateObject.SetCode(code)
}
}
func (s *State) AddRefund(gas *big.Int) {
s.refund.Add(s.refund, gas)
}
func (s *State) GetRefund() *big.Int {
var refund *big.Int
if s.MarkedTransition {
return new(big.Int)
} else if s.parent == nil {
refund = new(big.Int)
} else {
refund = s.parent.GetRefund()
}
return refund.Add(refund, s.refund)
}
func (s *State) GetState(address common.Address, stateAddress common.Hash) common.Hash {
stateObject := s.GetStateObject(address)
if stateObject != nil {
return stateObject.GetState(stateAddress)
}
return common.Hash{}
}
func (s *State) SetState(address common.Address, stateAddress common.Hash, value common.Hash) {
stateObject := s.GetOrNewStateObject(address)
if stateObject != nil {
stateObject.SetState(stateAddress, value)
}
}
func (s *State) Delete(address common.Address) bool {
stateObject := s.GetStateObject(address)
if stateObject != nil {
stateObject.MarkForDeletion()
stateObject.balance = new(big.Int)
return true
}
return false
}
func (s *State) HasAccount(addr common.Address) bool {
return s.GetStateObject(addr) != nil
}
func (s *State) Exist(address common.Address) bool {
return s.GetStateObject(address) != nil
}
func (s *State) IsDeleted(address common.Address) bool {
stateObject := s.GetStateObject(address)
if stateObject != nil {
return stateObject.remove
}
return false
}
func (s *State) GetAccount(address common.Address) vm.Account {
return s.GetStateObject(address)
}
func (s *State) CreateAccount(address common.Address) vm.Account {
return s.CreateStateObject(address)
}
func (s *State) AddLog(log *vm.Log) {
log.TxIndex = s.interInfo.txIdx
log.TxHash = s.interInfo.txHash
log.BlockHash = s.interInfo.blockHash
log.Index = s.logIdx
s.logs = append(s.logs, log)
s.logIdx++
}
// Logs returns the logs of it's entire ancestory chain or until
// the marked transition is found (state between transactions).
func (s *State) Logs() []*vm.Log {
var logs []*vm.Log
if s.MarkedTransition {
return nil
} else if s.parent != nil {
logs = s.parent.Logs()
}
return append(logs, s.logs...)
}
func (s *State) DeleteStateObject(stateObject *StateObject) {
stateObject.deleted = true
addr := stateObject.Address()
s.Trie.Delete(addr[:])
}
func (s *State) UpdateStateObject(stateObject *StateObject) {
addr := stateObject.Address()
data, err := rlp.EncodeToBytes(stateObject)
if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
}
s.Trie.Update(addr[:], data)
}
func (s *State) Reset(root common.Hash) error {
var (
err error
tr = s.Trie
)
if s.Trie.Hash() != root {
if tr, err = trie.NewSecure(root, s.Db); err != nil {
return err
}
}
*s = State{
Db: s.Db,
Trie: tr,
StateObjects: make(map[common.Address]*StateObject),
refund: new(big.Int),
}
return nil
}
// DeleteSuicides flags the suicided objects for deletion so that it
// won't be referenced again when called / queried up on.
//
// DeleteSuicides should not be used for consensus related updates
// under any circumstances.
func (s *State) DeleteSuicides() {
// Delete parents first
if s.parent != nil {
s.DeleteSuicides()
}
// Reset refund so that any used-gas calculations can use
// this method.
s.refund = new(big.Int)
for _, stateObject := range s.StateObjects {
if stateObject.dirty {
// If the object has been removed by a suicide
// flag the object as deleted.
if stateObject.remove {
stateObject.deleted = true
}
stateObject.dirty = false
}
}
}
// Fork preserve the given state and returns a handle to a new modifiable state
// that does not affect the preserved state.
func Fork(parent *State) *State {
return &State{
Db: parent.Db,
Trie: parent.Trie,
parent: parent,
StateObjects: make(map[common.Address]*StateObject),
refund: new(big.Int),
logIdx: parent.logIdx,
logs: nil,
}
}
func (s *State) Set(o *State) {
*s = *o
}
// Flatten flattens the state in to a single new state, including all changes of all ancestors.
func Flatten(s *State) *State {
// first commit the parent so we can overwrite changes
// later.
var flattenedState *State
if s.parent != nil {
flattenedState = Flatten(s.parent)
} else {
flattenedState = &State{
Db: s.Db,
Trie: s.Trie,
refund: new(big.Int),
StateObjects: make(map[common.Address]*StateObject),
}
}
for address, object := range s.StateObjects {
flattenedState.StateObjects[address] = object
}
flattenedState.logs = append(flattenedState.logs, s.logs...)
flattenedState.refund.Add(flattenedState.refund, s.refund)
return flattenedState
}
func IntermediateRoot(state *State) common.Hash {
s := Flatten(state)
for _, stateObject := range s.StateObjects {
if stateObject.dirty {
if stateObject.remove {
s.DeleteStateObject(stateObject)
} else {
stateObject.Update()
s.UpdateStateObject(stateObject)
}
stateObject.dirty = false
}
}
return s.Trie.Hash()
}