diff options
author | obscuren <geffobscura@gmail.com> | 2014-06-13 18:45:11 +0800 |
---|---|---|
committer | obscuren <geffobscura@gmail.com> | 2014-06-13 18:45:11 +0800 |
commit | d078e9b8c92fb3bd5789a8e39c169b19864e0a04 (patch) | |
tree | 34ce9defddbd84364e681362eef072fa5e5dab97 /ethchain/state_manager.go | |
parent | b855e5f7df194c84651d7cc7ee32d307a2fa0a2e (diff) | |
download | dexon-d078e9b8c92fb3bd5789a8e39c169b19864e0a04.tar.gz dexon-d078e9b8c92fb3bd5789a8e39c169b19864e0a04.tar.zst dexon-d078e9b8c92fb3bd5789a8e39c169b19864e0a04.zip |
Refactoring state transitioning
Diffstat (limited to 'ethchain/state_manager.go')
-rw-r--r-- | ethchain/state_manager.go | 335 |
1 files changed, 178 insertions, 157 deletions
diff --git a/ethchain/state_manager.go b/ethchain/state_manager.go index 7b44ba3b8..f44afecac 100644 --- a/ethchain/state_manager.go +++ b/ethchain/state_manager.go @@ -108,8 +108,86 @@ func (sm *StateManager) MakeStateObject(state *State, tx *Transaction) *StateObj return nil } -func (self *StateManager) ProcessTransaction(tx *Transaction, coinbase *StateObject, state *State, toContract bool) (gas *big.Int, err error) { - fmt.Printf("state root before update %x\n", state.Root()) +type StateTransition struct { + coinbase []byte + tx *Transaction + gas *big.Int + state *State + block *Block + + cb, rec, sen *StateObject +} + +func NewStateTransition(coinbase []byte, gas *big.Int, tx *Transaction, state *State, block *Block) *StateTransition { + return &StateTransition{coinbase, tx, new(big.Int), state, block, nil, nil, nil} +} + +func (self *StateTransition) Coinbase() *StateObject { + if self.cb != nil { + return self.cb + } + + self.cb = self.state.GetAccount(self.coinbase) + return self.cb +} +func (self *StateTransition) Sender() *StateObject { + if self.sen != nil { + return self.sen + } + + self.sen = self.state.GetAccount(self.tx.Sender()) + return self.sen +} +func (self *StateTransition) Receiver() *StateObject { + if self.tx.CreatesContract() { + return nil + } + + if self.rec != nil { + return self.rec + } + + self.rec = self.state.GetAccount(self.tx.Recipient) + return self.rec +} + +func (self *StateTransition) UseGas(amount *big.Int) error { + if self.gas.Cmp(amount) < 0 { + return OutOfGasError() + } + self.gas.Sub(self.gas, amount) + + return nil +} + +func (self *StateTransition) AddGas(amount *big.Int) { + self.gas.Add(self.gas, amount) +} + +func (self *StateTransition) BuyGas() error { + var err error + + sender := self.Sender() + if sender.Amount.Cmp(self.tx.GasValue()) < 0 { + return fmt.Errorf("Insufficient funds to pre-pay gas. Req %v, has %v", self.tx.GasValue(), self.tx.Value) + } + + coinbase := self.Coinbase() + err = coinbase.BuyGas(self.tx.Gas, self.tx.GasPrice) + if err != nil { + return err + } + self.state.UpdateStateObject(coinbase) + + self.AddGas(self.tx.Gas) + sender.SubAmount(self.tx.GasValue()) + + return nil +} + +func (self *StateManager) TransitionState(st *StateTransition) (err error) { + //snapshot := st.state.Snapshot() + defer func() { if r := recover(); r != nil { ethutil.Config.Log.Infoln(r) @@ -117,173 +195,144 @@ func (self *StateManager) ProcessTransaction(tx *Transaction, coinbase *StateObj } }() - gas = new(big.Int) - addGas := func(g *big.Int) { gas.Add(gas, g) } - addGas(GasTx) - - // Get the sender - sender := state.GetAccount(tx.Sender()) + var ( + tx = st.tx + sender = st.Sender() + receiver *StateObject + ) if sender.Nonce != tx.Nonce { - err = NonceError(tx.Nonce, sender.Nonce) - return + return NonceError(tx.Nonce, sender.Nonce) } sender.Nonce += 1 defer func() { - //state.UpdateStateObject(sender) // Notify all subscribers self.Ethereum.Reactor().Post("newTx:post", tx) }() - txTotalBytes := big.NewInt(int64(len(tx.Data))) - txTotalBytes.Div(txTotalBytes, ethutil.Big32) - addGas(new(big.Int).Mul(txTotalBytes, GasSStore)) + if err = st.BuyGas(); err != nil { + return err + } - rGas := new(big.Int).Set(gas) - rGas.Mul(gas, tx.GasPrice) + receiver = st.Receiver() - // Make sure there's enough in the sender's account. Having insufficient - // funds won't invalidate this transaction but simple ignores it. - totAmount := new(big.Int).Add(tx.Value, rGas) - if sender.Amount.Cmp(totAmount) < 0 { - state.UpdateStateObject(sender) - err = fmt.Errorf("[TXPL] Insufficient amount in sender's (%x) account", tx.Sender()) - return + if err = st.UseGas(GasTx); err != nil { + return err } - coinbase.BuyGas(gas, tx.GasPrice) - state.UpdateStateObject(coinbase) - - // Get the receiver - receiver := state.GetAccount(tx.Recipient) + dataPrice := big.NewInt(int64(len(tx.Data))) + dataPrice.Mul(dataPrice, GasData) + if err = st.UseGas(dataPrice); err != nil { + return err + } - // Send Tx to self - if bytes.Compare(tx.Recipient, tx.Sender()) == 0 { - // Subtract the fee - sender.SubAmount(rGas) - } else { - // Subtract the amount from the senders account - sender.SubAmount(totAmount) + if receiver == nil { // Contract + receiver = self.MakeStateObject(st.state, tx) + if receiver == nil { + return fmt.Errorf("ERR. Unable to create contract with transaction %v", tx) + } + } - // Add the amount to receivers account which should conclude this transaction - receiver.AddAmount(tx.Value) - state.UpdateStateObject(receiver) + if err = self.transferValue(st, sender, receiver); err != nil { + return err } - state.UpdateStateObject(sender) + if tx.CreatesContract() { + fmt.Println(Disassemble(receiver.Init())) + // Evaluate the initialization script + // and use the return value as the + // script section for the state object. + //script, gas, err = sm.Eval(state, contract.Init(), contract, tx, block) + code, err := self.Eval(st, receiver.Init(), receiver) + if err != nil { + return fmt.Errorf("Error during init script run %v", err) + } - ethutil.Config.Log.Infof("[TXPL] Processed Tx %x\n", tx.Hash()) + receiver.script = code + } - return + st.state.UpdateStateObject(sender) + st.state.UpdateStateObject(receiver) + + return nil } -// Apply transactions uses the transaction passed to it and applies them onto -// the current processing state. -func (sm *StateManager) ApplyTransactions(coinbase []byte, state *State, block *Block, txs []*Transaction) ([]*Receipt, []*Transaction) { - // Process each transaction/contract - var receipts []*Receipt - var validTxs []*Transaction - var ignoredTxs []*Transaction // Transactions which go over the gasLimit +func (self *StateManager) transferValue(st *StateTransition, sender, receiver *StateObject) error { + if sender.Amount.Cmp(st.tx.Value) < 0 { + return fmt.Errorf("Insufficient funds to transfer value. Req %v, has %v", st.tx.Value, sender.Amount) + } + + // Subtract the amount from the senders account + sender.SubAmount(st.tx.Value) + // Add the amount to receivers account which should conclude this transaction + receiver.AddAmount(st.tx.Value) - totalUsedGas := big.NewInt(0) + ethutil.Config.Log.Debugf("%x => %x (%v) %x\n", sender.Address()[:4], receiver.Address()[:4], st.tx.Value, st.tx.Hash()) - for _, tx := range txs { - usedGas, err := sm.ApplyTransaction(coinbase, state, block, tx) + return nil +} + +func (self *StateManager) ProcessTransactions(coinbase []byte, state *State, block, parent *Block, txs Transactions) (Receipts, Transactions, Transactions, error) { + var ( + receipts Receipts + handled, unhandled Transactions + totalUsedGas = big.NewInt(0) + err error + ) + +done: + for i, tx := range txs { + txGas := new(big.Int).Set(tx.Gas) + st := NewStateTransition(coinbase, tx.Gas, tx, state, block) + err = self.TransitionState(st) if err != nil { - if IsNonceErr(err) { - continue - } - if IsGasLimitErr(err) { - ignoredTxs = append(ignoredTxs, tx) - // We need to figure out if we want to do something with thse txes - ethutil.Config.Log.Debugln("Gastlimit:", err) + switch { + case IsNonceErr(err): + err = nil // ignore error continue - } + case IsGasLimitErr(err): + unhandled = txs[i:] - ethutil.Config.Log.Infoln(err) + break done + default: + ethutil.Config.Log.Infoln(err) + } } - accumelative := new(big.Int).Set(totalUsedGas.Add(totalUsedGas, usedGas)) + txGas.Sub(txGas, st.gas) + accumelative := new(big.Int).Set(totalUsedGas.Add(totalUsedGas, txGas)) receipt := &Receipt{tx, ethutil.CopyBytes(state.Root().([]byte)), accumelative} receipts = append(receipts, receipt) - validTxs = append(validTxs, tx) + handled = append(handled, tx) } fmt.Println("################# MADE\n", receipts, "\n############################") - // Update the total gas used for the block (to be mined) - block.GasUsed = totalUsedGas + parent.GasUsed = totalUsedGas - return receipts, validTxs + return receipts, handled, unhandled, err } -func (sm *StateManager) ApplyTransaction(coinbase []byte, state *State, block *Block, tx *Transaction) (totalGasUsed *big.Int, err error) { - /* - Applies transactions to the given state and creates new - state objects where needed. - - If said objects needs to be created - run the initialization script provided by the transaction and - assume there's a return value. The return value will be set to - the script section of the state object. - */ +func (self *StateManager) Eval(st *StateTransition, script []byte, context *StateObject) (ret []byte, err error) { var ( - addTotalGas = func(gas *big.Int) { totalGasUsed.Add(totalGasUsed, gas) } - gas = new(big.Int) - script []byte + tx = st.tx + block = st.block + initiator = st.Sender() ) - totalGasUsed = big.NewInt(0) - snapshot := state.Snapshot() - ca := state.GetAccount(coinbase) - // Apply the transaction to the current state - gas, err = sm.ProcessTransaction(tx, ca, state, false) - addTotalGas(gas) - - if tx.CreatesContract() { - if err == nil { - // Create a new state object and the transaction - // as it's data provider. - contract := sm.MakeStateObject(state, tx) - if contract != nil { - fmt.Println(Disassemble(contract.Init())) - // Evaluate the initialization script - // and use the return value as the - // script section for the state object. - script, gas, err = sm.EvalScript(state, contract.Init(), contract, tx, block) - addTotalGas(gas) - - if err != nil { - err = fmt.Errorf("[STATE] Error during init script run %v", err) - return - } - contract.script = script - state.UpdateStateObject(contract) - } else { - err = fmt.Errorf("[STATE] Unable to create contract") - } - } else { - err = fmt.Errorf("[STATE] contract creation tx: %v for sender %x", err, tx.Sender()) - } - } else { - // Find the state object at the "recipient" address. If - // there's an object attempt to run the script. - stateObject := state.GetStateObject(tx.Recipient) - if err == nil && stateObject != nil && len(stateObject.Script()) > 0 { - _, gas, err = sm.EvalScript(state, stateObject.Script(), stateObject, tx, block) - addTotalGas(gas) - } - } - - parent := sm.bc.GetBlock(block.PrevHash) - total := new(big.Int).Add(block.GasUsed, totalGasUsed) - limit := block.CalcGasLimit(parent) - if total.Cmp(limit) > 0 { - state.Revert(snapshot) - err = GasLimitError(total, limit) - } + closure := NewClosure(initiator, context, script, st.state, st.gas, tx.GasPrice) + vm := NewVm(st.state, self, RuntimeVars{ + Origin: initiator.Address(), + BlockNumber: block.BlockInfo().Number, + PrevHash: block.PrevHash, + Coinbase: block.Coinbase, + Time: block.Time, + Diff: block.Difficulty, + Value: tx.Value, + }) + ret, _, err = closure.Call(vm, tx.Data, nil) return } @@ -325,7 +374,8 @@ func (sm *StateManager) ProcessBlock(state *State, parent, block *Block, dontRea fmt.Println(block.Receipts()) // Process the transactions on to current block - sm.ApplyTransactions(block.Coinbase, state, parent, block.Transactions()) + //sm.ApplyTransactions(block.Coinbase, state, parent, block.Transactions()) + sm.ProcessTransactions(block.Coinbase, state, block, parent, block.Transactions()) // Block validation if err := sm.ValidateBlock(block); err != nil { @@ -464,35 +514,6 @@ func (sm *StateManager) Stop() { sm.bc.Stop() } -func (sm *StateManager) EvalScript(state *State, script []byte, object *StateObject, tx *Transaction, block *Block) (ret []byte, gas *big.Int, err error) { - account := state.GetAccount(tx.Sender()) - - err = account.ConvertGas(tx.Gas, tx.GasPrice) - if err != nil { - ethutil.Config.Log.Debugln(err) - return - } - - closure := NewClosure(account, object, script, state, tx.Gas, tx.GasPrice) - vm := NewVm(state, sm, RuntimeVars{ - Origin: account.Address(), - BlockNumber: block.BlockInfo().Number, - PrevHash: block.PrevHash, - Coinbase: block.Coinbase, - Time: block.Time, - Diff: block.Difficulty, - Value: tx.Value, - //Price: tx.GasPrice, - }) - ret, gas, err = closure.Call(vm, tx.Data, nil) - - // Update the account (refunds) - state.UpdateStateObject(account) - state.UpdateStateObject(object) - - return -} - func (sm *StateManager) notifyChanges(state *State) { for addr, stateObject := range state.manifest.objectChanges { sm.Ethereum.Reactor().Post("object:"+addr, stateObject) |