Merged accounts and contracts in to StateObject

* Account removed
* Contract removed
* Address state changed to CachedStateObject
* Added StateObject
This commit is contained in:
obscuren 2014-04-16 04:06:51 +02:00
parent ca13e3b105
commit 9c6aca7893
11 changed files with 219 additions and 300 deletions

View File

@ -1,76 +0,0 @@
package ethchain
import (
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
type Account struct {
address []byte
Amount *big.Int
Nonce uint64
}
func NewAccount(address []byte, amount *big.Int) *Account {
return &Account{address, amount, 0}
}
func NewAccountFromData(address, data []byte) *Account {
account := &Account{address: address}
account.RlpDecode(data)
return account
}
func (a *Account) AddFee(fee *big.Int) {
a.AddFunds(fee)
}
func (a *Account) AddFunds(funds *big.Int) {
a.Amount.Add(a.Amount, funds)
}
func (a *Account) Address() []byte {
return a.address
}
// Implements Callee
func (a *Account) ReturnGas(value *big.Int, state *State) {
// Return the value back to the sender
a.AddFunds(value)
state.UpdateAccount(a.address, a)
}
func (a *Account) RlpEncode() []byte {
return ethutil.Encode([]interface{}{a.Amount, a.Nonce})
}
func (a *Account) RlpDecode(data []byte) {
decoder := ethutil.NewValueFromBytes(data)
a.Amount = decoder.Get(0).BigInt()
a.Nonce = decoder.Get(1).Uint()
}
type AddrStateStore struct {
states map[string]*AccountState
}
func NewAddrStateStore() *AddrStateStore {
return &AddrStateStore{states: make(map[string]*AccountState)}
}
func (s *AddrStateStore) Add(addr []byte, account *Account) *AccountState {
state := &AccountState{Nonce: account.Nonce, Account: account}
s.states[string(addr)] = state
return state
}
func (s *AddrStateStore) Get(addr []byte) *AccountState {
return s.states[string(addr)]
}
type AccountState struct {
Nonce uint64
Account *Account
}

View File

@ -1,8 +0,0 @@
package ethchain
import (
"testing"
)
func TestAddressState(t *testing.T) {
}

View File

@ -142,12 +142,13 @@ func (block *Block) PayFee(addr []byte, fee *big.Int) bool {
data := block.state.trie.Get(string(block.Coinbase))
// Get the ether (Coinbase) and add the fee (gief fee to miner)
ether := NewAccountFromData(block.Coinbase, []byte(data))
account := NewStateObjectFromBytes(block.Coinbase, []byte(data))
base = new(big.Int)
ether.Amount = base.Add(ether.Amount, fee)
account.Amount = base.Add(account.Amount, fee)
block.state.trie.Update(string(block.Coinbase), string(ether.RlpEncode()))
//block.state.trie.Update(string(block.Coinbase), string(ether.RlpEncode()))
block.state.UpdateStateObject(account)
return true
}

View File

@ -262,9 +262,9 @@ func AddTestNetFunds(block *Block) {
} {
//log.Println("2^200 Wei to", addr)
codedAddr := ethutil.FromHex(addr)
addr := block.state.GetAccount(codedAddr)
addr.Amount = ethutil.BigPow(2, 200)
block.state.UpdateAccount(codedAddr, addr)
account := block.state.GetAccount(codedAddr)
account.Amount = ethutil.BigPow(2, 200)
block.state.UpdateStateObject(account)
}
}

View File

@ -1,119 +0,0 @@
package ethchain
import (
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
type Contract struct {
Amount *big.Int
Nonce uint64
//state *ethutil.Trie
state *State
address []byte
script []byte
initScript []byte
}
func NewContract(address []byte, Amount *big.Int, root []byte) *Contract {
contract := &Contract{address: address, Amount: Amount, Nonce: 0}
contract.state = NewState(ethutil.NewTrie(ethutil.Config.Db, string(root)))
return contract
}
func NewContractFromBytes(address, data []byte) *Contract {
contract := &Contract{address: address}
contract.RlpDecode(data)
return contract
}
func (c *Contract) Addr(addr []byte) *ethutil.Value {
return ethutil.NewValueFromBytes([]byte(c.state.trie.Get(string(addr))))
}
func (c *Contract) SetAddr(addr []byte, value interface{}) {
c.state.trie.Update(string(addr), string(ethutil.NewValue(value).Encode()))
}
func (c *Contract) State() *State {
return c.state
}
func (c *Contract) GetMem(num *big.Int) *ethutil.Value {
nb := ethutil.BigToBytes(num, 256)
return c.Addr(nb)
}
func (c *Contract) GetInstr(pc *big.Int) *ethutil.Value {
if int64(len(c.script)-1) < pc.Int64() {
return ethutil.NewValue(0)
}
return ethutil.NewValueFromBytes([]byte{c.script[pc.Int64()]})
}
func (c *Contract) SetMem(num *big.Int, val *ethutil.Value) {
addr := ethutil.BigToBytes(num, 256)
c.state.trie.Update(string(addr), string(val.Encode()))
}
// Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *Contract) ReturnGas(val *big.Int, state *State) {
c.Amount.Add(c.Amount, val)
}
func (c *Contract) Address() []byte {
return c.address
}
func (c *Contract) Script() []byte {
return c.script
}
func (c *Contract) Init() []byte {
return c.initScript
}
func (c *Contract) RlpEncode() []byte {
return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root, c.script, c.initScript})
}
func (c *Contract) RlpDecode(data []byte) {
decoder := ethutil.NewValueFromBytes(data)
c.Amount = decoder.Get(0).BigInt()
c.Nonce = decoder.Get(1).Uint()
c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
c.script = decoder.Get(3).Bytes()
c.initScript = decoder.Get(4).Bytes()
}
func MakeContract(tx *Transaction, state *State) *Contract {
// Create contract if there's no recipient
if tx.IsContract() {
addr := tx.Hash()[12:]
value := tx.Value
contract := NewContract(addr, value, []byte(""))
state.trie.Update(string(addr), string(contract.RlpEncode()))
contract.script = tx.Data
contract.initScript = tx.Init
/*
for i, val := range tx.Data {
if len(val) > 0 {
bytNum := ethutil.BigToBytes(big.NewInt(int64(i)), 256)
contract.state.trie.Update(string(bytNum), string(ethutil.Encode(val)))
}
}
*/
state.trie.Update(string(addr), string(contract.RlpEncode()))
return contract
}
return nil
}

View File

@ -10,7 +10,7 @@ type KeyPair struct {
PublicKey []byte
// The associated account
account *Account
account *StateObject
state *State
}
@ -24,7 +24,7 @@ func (k *KeyPair) Address() []byte {
return ethutil.Sha3Bin(k.PublicKey[1:])[12:]
}
func (k *KeyPair) Account() *Account {
func (k *KeyPair) Account() *StateObject {
if k.account == nil {
k.account = k.state.GetAccount(k.Address())
}

View File

@ -47,23 +47,14 @@ func (s *State) Purge() int {
return s.trie.NewIterator().Purge()
}
func (s *State) GetContract(addr []byte) *Contract {
func (s *State) GetContract(addr []byte) *StateObject {
data := s.trie.Get(string(addr))
if data == "" {
return nil
}
// Whet get contract is called the retrieved value might
// be an account. The StateManager uses this to check
// to see if the address a tx was sent to is a contract
// or an account
value := ethutil.NewValueFromBytes([]byte(data))
if value.Len() == 2 {
return nil
}
// build contract
contract := NewContractFromBytes(addr, []byte(data))
contract := NewStateObjectFromBytes(addr, []byte(data))
// Check if there's a cached state for this contract
cachedState := s.states[string(addr)]
@ -77,28 +68,17 @@ func (s *State) GetContract(addr []byte) *Contract {
return contract
}
func (s *State) UpdateContract(contract *Contract) {
addr := contract.Address()
s.states[string(addr)] = contract.state
s.trie.Update(string(addr), string(contract.RlpEncode()))
}
func (s *State) GetAccount(addr []byte) (account *Account) {
func (s *State) GetAccount(addr []byte) (account *StateObject) {
data := s.trie.Get(string(addr))
if data == "" {
account = NewAccount(addr, big.NewInt(0))
} else {
account = NewAccountFromData(addr, []byte(data))
account = NewStateObjectFromBytes(addr, []byte(data))
}
return
}
func (s *State) UpdateAccount(addr []byte, account *Account) {
s.trie.Update(string(addr), string(account.RlpEncode()))
}
func (s *State) Cmp(other *State) bool {
return s.trie.Cmp(other.trie)
}
@ -119,7 +99,7 @@ const (
// Returns the object stored at key and the type stored at key
// Returns nil if nothing is stored
func (s *State) Get(key []byte) (*ethutil.Value, ObjType) {
func (s *State) GetStateObject(key []byte) (*ethutil.Value, ObjType) {
// Fetch data from the trie
data := s.trie.Get(string(key))
// Returns the nil type, indicating nothing could be retrieved.
@ -145,6 +125,17 @@ func (s *State) Get(key []byte) (*ethutil.Value, ObjType) {
return val, typ
}
// Updates any given state object
func (s *State) UpdateStateObject(object *StateObject) {
addr := object.Address()
if object.state != nil {
s.states[string(addr)] = object.state
}
s.trie.Update(string(addr), string(object.RlpEncode()))
}
func (s *State) Put(key, object []byte) {
s.trie.Update(string(key), string(object))
}
@ -152,27 +143,3 @@ func (s *State) Put(key, object []byte) {
func (s *State) Root() interface{} {
return s.trie.Root
}
// Script compilation functions
// Compiles strings to machine code
func Compile(code []string) (script []string) {
script = make([]string, len(code))
for i, val := range code {
instr, _ := ethutil.CompileInstr(val)
script[i] = string(instr)
}
return
}
func CompileToValues(code []string) (script []*ethutil.Value) {
script = make([]*ethutil.Value, len(code))
for i, val := range code {
instr, _ := ethutil.CompileInstr(val)
script[i] = ethutil.NewValue(instr)
}
return
}

View File

@ -30,7 +30,7 @@ type StateManager struct {
bc *BlockChain
// States for addresses. You can watch any address
// at any given time
addrStateStore *AddrStateStore
stateObjectCache *StateObjectCache
// Stack for processing contracts
stack *Stack
@ -54,12 +54,12 @@ type StateManager struct {
func NewStateManager(ethereum EthManager) *StateManager {
sm := &StateManager{
stack: NewStack(),
mem: make(map[string]*big.Int),
Pow: &EasyPow{},
Ethereum: ethereum,
addrStateStore: NewAddrStateStore(),
bc: ethereum.BlockChain(),
stack: NewStack(),
mem: make(map[string]*big.Int),
Pow: &EasyPow{},
Ethereum: ethereum,
stateObjectCache: NewStateObjectCache(),
bc: ethereum.BlockChain(),
}
sm.procState = ethereum.BlockChain().CurrentBlock.State()
return sm
@ -70,18 +70,18 @@ func (sm *StateManager) ProcState() *State {
}
// Watches any given address and puts it in the address state store
func (sm *StateManager) WatchAddr(addr []byte) *AccountState {
func (sm *StateManager) WatchAddr(addr []byte) *CachedStateObject {
//XXX account := sm.bc.CurrentBlock.state.GetAccount(addr)
account := sm.procState.GetAccount(addr)
return sm.addrStateStore.Add(addr, account)
return sm.stateObjectCache.Add(addr, account)
}
func (sm *StateManager) GetAddrState(addr []byte) *AccountState {
account := sm.addrStateStore.Get(addr)
func (sm *StateManager) GetAddrState(addr []byte) *CachedStateObject {
account := sm.stateObjectCache.Get(addr)
if account == nil {
a := sm.procState.GetAccount(addr)
account = &AccountState{Nonce: a.Nonce, Account: a}
account = &CachedStateObject{Nonce: a.Nonce, Object: a}
}
return account
@ -116,7 +116,7 @@ func (sm *StateManager) ApplyTransactions(block *Block, txs []*Transaction) {
if contract := sm.procState.GetContract(tx.Recipient); contract != nil {
err = sm.Ethereum.TxPool().ProcessTransaction(tx, block, true)
if err == nil {
sm.ProcessContract(contract, tx, block)
sm.EvalScript(contract.Script(), contract, tx, block)
}
} else {
err = sm.Ethereum.TxPool().ProcessTransaction(tx, block, false)
@ -180,7 +180,6 @@ func (sm *StateManager) ProcessBlock(block *Block, dontReact bool) error {
return err
}
// if !sm.compState.Cmp(sm.procState)
if !sm.compState.Cmp(sm.procState) {
return fmt.Errorf("Invalid merkle root. Expected %x, got %x", sm.compState.trie.Root, sm.procState.trie.Root)
}
@ -190,9 +189,6 @@ func (sm *StateManager) ProcessBlock(block *Block, dontReact bool) error {
// Sync the current block's state to the database and cancelling out the deferred Undo
sm.procState.Sync()
// Broadcast the valid block back to the wire
//sm.Ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val})
// Add the block to the chain
sm.bc.Add(block)
@ -282,22 +278,20 @@ func CalculateUncleReward(block *Block) *big.Int {
}
func (sm *StateManager) AccumelateRewards(block *Block) error {
// Get the coinbase rlp data
acc := sm.procState.GetAccount(block.Coinbase)
// Get the account associated with the coinbase
account := sm.procState.GetAccount(block.Coinbase)
// Reward amount of ether to the coinbase address
acc.AddFee(CalculateBlockReward(block, len(block.Uncles)))
account.AddAmount(CalculateBlockReward(block, len(block.Uncles)))
addr := make([]byte, len(block.Coinbase))
copy(addr, block.Coinbase)
sm.procState.UpdateAccount(addr, acc)
sm.procState.UpdateStateObject(account)
for _, uncle := range block.Uncles {
uncleAddr := sm.procState.GetAccount(uncle.Coinbase)
uncleAddr.AddFee(CalculateUncleReward(uncle))
uncleAccount := sm.procState.GetAccount(uncle.Coinbase)
uncleAccount.AddAmount(CalculateUncleReward(uncle))
//processor.state.UpdateAccount(uncle.Coinbase, uncleAddr)
sm.procState.UpdateAccount(uncle.Coinbase, uncleAddr)
sm.procState.UpdateStateObject(uncleAccount)
}
return nil
@ -307,7 +301,7 @@ func (sm *StateManager) Stop() {
sm.bc.Stop()
}
func (sm *StateManager) ProcessContract(contract *Contract, tx *Transaction, block *Block) {
func (sm *StateManager) EvalScript(script []byte, object *StateObject, tx *Transaction, block *Block) {
// Recovering function in case the VM had any errors
defer func() {
if r := recover(); r != nil {
@ -316,7 +310,7 @@ func (sm *StateManager) ProcessContract(contract *Contract, tx *Transaction, blo
}()
caller := sm.procState.GetAccount(tx.Sender())
closure := NewClosure(caller, contract, contract.script, sm.procState, tx.Gas, tx.Value)
closure := NewClosure(caller, object, script, sm.procState, tx.Gas, tx.Value)
vm := NewVm(sm.procState, RuntimeVars{
Origin: caller.Address(),
BlockNumber: block.BlockInfo().Number,
@ -324,11 +318,9 @@ func (sm *StateManager) ProcessContract(contract *Contract, tx *Transaction, blo
Coinbase: block.Coinbase,
Time: block.Time,
Diff: block.Difficulty,
// XXX Tx data? Could be just an argument to the closure instead
TxData: nil,
})
closure.Call(vm, nil, nil)
// Update the account (refunds)
sm.procState.UpdateAccount(tx.Sender(), caller)
sm.procState.UpdateStateObject(caller)
}

162
ethchain/state_object.go Normal file
View File

@ -0,0 +1,162 @@
package ethchain
import (
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
type StateObject struct {
// Address of the object
address []byte
// Shared attributes
Amount *big.Int
Nonce uint64
// Contract related attributes
state *State
script []byte
initScript []byte
}
func NewContract(address []byte, Amount *big.Int, root []byte) *StateObject {
contract := &StateObject{address: address, Amount: Amount, Nonce: 0}
contract.state = NewState(ethutil.NewTrie(ethutil.Config.Db, string(root)))
return contract
}
// Returns a newly created account
func NewAccount(address []byte, amount *big.Int) *StateObject {
account := &StateObject{address: address, Amount: amount, Nonce: 0}
return account
}
func NewStateObjectFromBytes(address, data []byte) *StateObject {
object := &StateObject{address: address}
object.RlpDecode(data)
return object
}
func (c *StateObject) Addr(addr []byte) *ethutil.Value {
return ethutil.NewValueFromBytes([]byte(c.state.trie.Get(string(addr))))
}
func (c *StateObject) SetAddr(addr []byte, value interface{}) {
c.state.trie.Update(string(addr), string(ethutil.NewValue(value).Encode()))
}
func (c *StateObject) State() *State {
return c.state
}
func (c *StateObject) GetMem(num *big.Int) *ethutil.Value {
nb := ethutil.BigToBytes(num, 256)
return c.Addr(nb)
}
func (c *StateObject) GetInstr(pc *big.Int) *ethutil.Value {
if int64(len(c.script)-1) < pc.Int64() {
return ethutil.NewValue(0)
}
return ethutil.NewValueFromBytes([]byte{c.script[pc.Int64()]})
}
func (c *StateObject) SetMem(num *big.Int, val *ethutil.Value) {
addr := ethutil.BigToBytes(num, 256)
c.state.trie.Update(string(addr), string(val.Encode()))
}
// Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *StateObject) ReturnGas(val *big.Int, state *State) {
c.AddAmount(val)
}
func (c *StateObject) AddAmount(amount *big.Int) {
c.Amount.Add(c.Amount, amount)
}
func (c *StateObject) SubAmount(amount *big.Int) {
c.Amount.Sub(c.Amount, amount)
}
func (c *StateObject) Address() []byte {
return c.address
}
func (c *StateObject) Script() []byte {
return c.script
}
func (c *StateObject) Init() []byte {
return c.initScript
}
func (c *StateObject) RlpEncode() []byte {
var root interface{}
if c.state != nil {
root = c.state.trie.Root
} else {
root = nil
}
return ethutil.Encode([]interface{}{c.Amount, c.Nonce, root, c.script})
}
func (c *StateObject) RlpDecode(data []byte) {
decoder := ethutil.NewValueFromBytes(data)
c.Amount = decoder.Get(0).BigInt()
c.Nonce = decoder.Get(1).Uint()
c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
c.script = decoder.Get(3).Bytes()
}
func MakeContract(tx *Transaction, state *State) *StateObject {
// Create contract if there's no recipient
if tx.IsContract() {
// FIXME
addr := tx.Hash()[12:]
value := tx.Value
contract := NewContract(addr, value, []byte(""))
state.UpdateStateObject(contract)
contract.script = tx.Data
contract.initScript = tx.Init
state.UpdateStateObject(contract)
return contract
}
return nil
}
// The cached state and state object cache are helpers which will give you somewhat
// control over the nonce. When creating new transactions you're interested in the 'next'
// nonce rather than the current nonce. This to avoid creating invalid-nonce transactions.
type StateObjectCache struct {
cachedObjects map[string]*CachedStateObject
}
func NewStateObjectCache() *StateObjectCache {
return &StateObjectCache{cachedObjects: make(map[string]*CachedStateObject)}
}
func (s *StateObjectCache) Add(addr []byte, object *StateObject) *CachedStateObject {
state := &CachedStateObject{Nonce: object.Nonce, Object: object}
s.cachedObjects[string(addr)] = state
return state
}
func (s *StateObjectCache) Get(addr []byte) *CachedStateObject {
return s.cachedObjects[string(addr)]
}
type CachedStateObject struct {
Nonce uint64
Object *StateObject
}

View File

@ -23,8 +23,8 @@ type Transaction struct {
contractCreation bool
}
func NewContractCreationTx(value, gasprice *big.Int, data []byte) *Transaction {
return &Transaction{Value: value, Gasprice: gasprice, Data: data, contractCreation: true}
func NewContractCreationTx(value, gasprice *big.Int, script []byte, init []byte) *Transaction {
return &Transaction{Value: value, Gasprice: gasprice, Data: script, Init: init, contractCreation: true}
}
func NewTransactionMessage(to []byte, value, gasprice, gas *big.Int, data []byte) *Transaction {

View File

@ -118,20 +118,20 @@ func (pool *TxPool) ProcessTransaction(tx *Transaction, block *Block, toContract
// Send Tx to self
if bytes.Compare(tx.Recipient, tx.Sender()) == 0 {
// Subtract the fee
sender.Amount.Sub(sender.Amount, new(big.Int).Mul(TxFee, TxFeeRat))
sender.SubAmount(new(big.Int).Mul(TxFee, TxFeeRat))
} else if toContract {
sender.Amount.Sub(sender.Amount, new(big.Int).Mul(TxFee, TxFeeRat))
sender.SubAmount(new(big.Int).Mul(TxFee, TxFeeRat))
} else {
// Subtract the amount from the senders account
sender.Amount.Sub(sender.Amount, totAmount)
sender.SubAmount(totAmount)
// Add the amount to receivers account which should conclude this transaction
receiver.Amount.Add(receiver.Amount, tx.Value)
receiver.AddAmount(tx.Value)
block.state.UpdateAccount(tx.Recipient, receiver)
block.state.UpdateStateObject(receiver)
}
block.state.UpdateAccount(tx.Sender(), sender)
block.state.UpdateStateObject(sender)
log.Printf("[TXPL] Processed Tx %x\n", tx.Hash())
@ -151,7 +151,7 @@ func (pool *TxPool) ValidateTransaction(tx *Transaction) error {
// Get the sender
accountState := pool.Ethereum.StateManager().GetAddrState(tx.Sender())
sender := accountState.Account
sender := accountState.Object
totAmount := new(big.Int).Add(tx.Value, new(big.Int).Mul(TxFee, TxFeeRat))
// Make sure there's enough in the sender's account. Having insufficient