mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-29 22:32:56 +00:00
In this commit, the RPC API is adapted to run on top of the new Go API.
This required lots of changes to many packages, but has a few side
benefits:
- Ethereum and LightEthereum can now be used as a contract backend.
- Some duplicated code could be removed (there is added duplication in
other places though)
- It is now much easier to see which operations are unsupported with the
light client. Package les previously relied on the full node RPC API
backend, which masked several issues because the RPC API performed
direct access to the chain database.
Changes to packages in detail:
accounts/abi/bind:
- Contract call boilerplate has moved to package core.
cmd/utils:
- les now inherits the event.TypeMux from the Node instance
contracts/release:
- The ReleaseService now uses Ethereum and LightEthereum as backend.
core:
- MissingNumber is exposed so it can be used in package eth.
- GetTransaction now returns the index as an int, for convenience
reasons.
- ApplyCallMessage has been added as the new one and only
implementation of read-only contract calls.
- TxPool exposes NonceAt instead of the more general accessor for the
ManagedState.
core/types:
- Signer.SignECDSA is gone (it was basically unused).
- WithSignature doesn't return an error anymore (all implementations panic for
invalid length). I made this change to avoid code duplication in the API.
eth:
- EthApiBackend is gone. In its place, Ethereum gains many new methods
which implement a large portion of the new Go API. It does not
yet support event subscriptions and log filtering.
- Some accessors for internal objects are gone.
- ethapi.PrivateDebugAPI and ethapi.TxPoolDebugAPI are now created in
package eth for dependency reasons.
eth/downloader:
- Progress returns a pointer to simplify callers.
eth/filters:
- The Backend interface is simpler and uses the stable Go API where
possible. The new BlockReceipts method saves one DB read because
BlockReceipts also takes a block number argument.
- ChainDB is no longer passed via the Backend interface.
- EventSystem now relies on HeaderByHash for reorgs in light client mode
instead of reading from the chain database.
eth/gasprice:
- The LightPriceOracle now uses ethereum.ChainReader instead of
ethapi.Backend.
ethclient:
- TransactionByHash is adapted for the last-minute API change which
adds the isPending return value.
internal/ethapi:
- PublicTxPoolAPI is now called TxPoolDebugAPI, moved to its own file
and talks to the transaction pool instead of using the main Backend.
- The API no longer accesses the chain database directly. All access
is mediated through Backend methods.
- The backend is now split into three interfaces.
Implementing Backend is mandatory but does not require the pending
state. The other two (PendingState, TransactionInclusionBlock) are
optional and discovered at runtime.
les:
- LesApiBackend is gone, LightEthereum gets all the methods.
- Weird accessors copied from package eth are now gone as well.
light:
- TxPool.Stats now returns a queued count of zero. It implements the
ethapi.TxPool interface and can be used with TxPoolDebugAPI.
333 lines
9.6 KiB
Go
333 lines
9.6 KiB
Go
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
|
|
ethereum "github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
"github.com/ethereum/go-ethereum/logger/glog"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
var (
|
|
Big0 = big.NewInt(0)
|
|
)
|
|
|
|
/*
|
|
The State Transitioning Model
|
|
|
|
A state transition is a change made when a transaction is applied to the current world state
|
|
The state transitioning model does all all the necessary work to work out a valid new state root.
|
|
|
|
1) Nonce handling
|
|
2) Pre pay gas
|
|
3) Create a new state object if the recipient is \0*32
|
|
4) Value transfer
|
|
== If contract creation ==
|
|
4a) Attempt to run transaction data
|
|
4b) If valid, use result as code for the new state object
|
|
== end ==
|
|
5) Run Script section
|
|
6) Derive new state root
|
|
*/
|
|
type StateTransition struct {
|
|
gp *GasPool
|
|
msg Message
|
|
gas, gasPrice *big.Int
|
|
initialGas *big.Int
|
|
value *big.Int
|
|
data []byte
|
|
state vm.Database
|
|
|
|
env vm.Environment
|
|
}
|
|
|
|
// Message represents a message sent to a contract.
|
|
type Message interface {
|
|
From() common.Address
|
|
//FromFrontier() (common.Address, error)
|
|
To() *common.Address
|
|
|
|
GasPrice() *big.Int
|
|
Gas() *big.Int
|
|
Value() *big.Int
|
|
|
|
Nonce() uint64
|
|
CheckNonce() bool
|
|
Data() []byte
|
|
}
|
|
|
|
func MessageCreatesContract(msg Message) bool {
|
|
return msg.To() == nil
|
|
}
|
|
|
|
// IntrinsicGas computes the 'intrinsic gas' for a message
|
|
// with the given data.
|
|
func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int {
|
|
igas := new(big.Int)
|
|
if contractCreation && homestead {
|
|
igas.Set(params.TxGasContractCreation)
|
|
} else {
|
|
igas.Set(params.TxGas)
|
|
}
|
|
if len(data) > 0 {
|
|
var nz int64
|
|
for _, byt := range data {
|
|
if byt != 0 {
|
|
nz++
|
|
}
|
|
}
|
|
m := big.NewInt(nz)
|
|
m.Mul(m, params.TxDataNonZeroGas)
|
|
igas.Add(igas, m)
|
|
m.SetInt64(int64(len(data)) - nz)
|
|
m.Mul(m, params.TxDataZeroGas)
|
|
igas.Add(igas, m)
|
|
}
|
|
return igas
|
|
}
|
|
|
|
// NewStateTransition initialises and returns a new state transition object.
|
|
func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTransition {
|
|
return &StateTransition{
|
|
gp: gp,
|
|
env: env,
|
|
msg: msg,
|
|
gas: new(big.Int),
|
|
gasPrice: msg.GasPrice(),
|
|
initialGas: new(big.Int),
|
|
value: msg.Value(),
|
|
data: msg.Data(),
|
|
state: env.Db(),
|
|
}
|
|
}
|
|
|
|
// ApplyMessage computes the new state by applying the given message
|
|
// against the old state within the environment.
|
|
//
|
|
// ApplyMessage returns the bytes returned by any EVM execution (if it took place),
|
|
// the gas used (which includes gas refunds) and an error if it failed. An error always
|
|
// indicates a core error meaning that the message would always fail for that particular
|
|
// state and would never be accepted within a block.
|
|
func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
|
|
st := NewStateTransition(env, msg, gp)
|
|
|
|
ret, _, gasUsed, err := st.TransitionDb()
|
|
return ret, gasUsed, err
|
|
}
|
|
|
|
// ApplyCallMessage runs the given message as a read-only contract call.
|
|
//
|
|
// It modifies the environment to provide a near infinite amount of ether to the sender
|
|
// account and runs the message with the amount of gas configured in the message. This
|
|
// function is meant to be used outside of regular block processing.
|
|
//
|
|
// It returns the bytes returned by any EVM execution (if it took place), the gas used
|
|
// (not including gas refunds) and an error if it failed. An error always indicates a core
|
|
// error meaning that the message would always fail for that particular state and would
|
|
// never be accepted within a block.
|
|
func ApplyCallMessage(call ethereum.CallMsg, newEnv func(Message) vm.Environment) ([]byte, *big.Int, error) {
|
|
// Ensure message is initialized properly.
|
|
if call.GasPrice == nil {
|
|
call.GasPrice = big.NewInt(1)
|
|
}
|
|
if call.Gas == nil || call.Gas.BitLen() == 0 {
|
|
call.Gas = big.NewInt(50000000)
|
|
}
|
|
if call.Value == nil {
|
|
call.Value = new(big.Int)
|
|
}
|
|
msg := callmsg{call}
|
|
env := newEnv(msg)
|
|
// Provide ether to the caller account.
|
|
have := env.Db().GetBalance(call.From)
|
|
need := new(big.Int).Sub(common.MaxBig, have)
|
|
env.Db().AddBalance(call.From, need)
|
|
// Execute the call.
|
|
gaspool := new(GasPool).AddGas(call.Gas)
|
|
ret, gasUsed, _, err := NewStateTransition(env, msg, gaspool).TransitionDb()
|
|
return ret, gasUsed, err
|
|
}
|
|
|
|
func (self *StateTransition) from() vm.Account {
|
|
f := self.msg.From()
|
|
if !self.state.Exist(f) {
|
|
return self.state.CreateAccount(f)
|
|
}
|
|
return self.state.GetAccount(f)
|
|
}
|
|
|
|
func (self *StateTransition) to() vm.Account {
|
|
if self.msg == nil {
|
|
return nil
|
|
}
|
|
to := self.msg.To()
|
|
if to == nil {
|
|
return nil // contract creation
|
|
}
|
|
|
|
if !self.state.Exist(*to) {
|
|
return self.state.CreateAccount(*to)
|
|
}
|
|
return self.state.GetAccount(*to)
|
|
}
|
|
|
|
func (self *StateTransition) useGas(amount *big.Int) error {
|
|
if self.gas.Cmp(amount) < 0 {
|
|
return vm.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 {
|
|
mgas := self.msg.Gas()
|
|
mgval := new(big.Int).Mul(mgas, self.gasPrice)
|
|
|
|
sender := self.from()
|
|
if sender.Balance().Cmp(mgval) < 0 {
|
|
return fmt.Errorf("insufficient ETH for gas (%x). Req %v, has %v", sender.Address().Bytes()[:4], mgval, sender.Balance())
|
|
}
|
|
if err := self.gp.SubGas(mgas); err != nil {
|
|
return err
|
|
}
|
|
self.addGas(mgas)
|
|
self.initialGas.Set(mgas)
|
|
sender.SubBalance(mgval)
|
|
return nil
|
|
}
|
|
|
|
func (self *StateTransition) preCheck() (err error) {
|
|
msg := self.msg
|
|
sender := self.from()
|
|
|
|
// Make sure this transaction's nonce is correct
|
|
if msg.CheckNonce() {
|
|
if n := self.state.GetNonce(sender.Address()); n != msg.Nonce() {
|
|
return NonceError(msg.Nonce(), n)
|
|
}
|
|
}
|
|
|
|
// Pre-pay gas
|
|
if err = self.buyGas(); err != nil {
|
|
if IsGasLimitErr(err) {
|
|
return err
|
|
}
|
|
return InvalidTxError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TransitionDb will move the state by applying the message against the given environment.
|
|
func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, err error) {
|
|
if err = self.preCheck(); err != nil {
|
|
return
|
|
}
|
|
msg := self.msg
|
|
sender := self.from() // err checked in preCheck
|
|
|
|
homestead := self.env.ChainConfig().IsHomestead(self.env.BlockNumber())
|
|
contractCreation := MessageCreatesContract(msg)
|
|
// Pay intrinsic gas
|
|
if err = self.useGas(IntrinsicGas(self.data, contractCreation, homestead)); err != nil {
|
|
return nil, nil, nil, InvalidTxError(err)
|
|
}
|
|
|
|
vmenv := self.env
|
|
//var addr common.Address
|
|
if contractCreation {
|
|
ret, _, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value)
|
|
if homestead && err == vm.CodeStoreOutOfGasError {
|
|
self.gas = Big0
|
|
}
|
|
|
|
if err != nil {
|
|
ret = nil
|
|
glog.V(logger.Core).Infoln("VM create err:", err)
|
|
}
|
|
} else {
|
|
// Increment the nonce for the next transaction
|
|
self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1)
|
|
ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value)
|
|
if err != nil {
|
|
glog.V(logger.Core).Infoln("VM call err:", err)
|
|
}
|
|
}
|
|
|
|
if err != nil && IsValueTransferErr(err) {
|
|
return nil, nil, nil, InvalidTxError(err)
|
|
}
|
|
|
|
// We aren't interested in errors here. Errors returned by the VM are non-consensus errors and therefor shouldn't bubble up
|
|
if err != nil {
|
|
err = nil
|
|
}
|
|
|
|
requiredGas = new(big.Int).Set(self.gasUsed())
|
|
|
|
self.refundGas()
|
|
self.state.AddBalance(self.env.Coinbase(), new(big.Int).Mul(self.gasUsed(), self.gasPrice))
|
|
|
|
return ret, requiredGas, self.gasUsed(), err
|
|
}
|
|
|
|
func (self *StateTransition) refundGas() {
|
|
// Return eth for remaining gas to the sender account,
|
|
// exchanged at the original rate.
|
|
sender := self.from() // err already checked
|
|
remaining := new(big.Int).Mul(self.gas, self.gasPrice)
|
|
sender.AddBalance(remaining)
|
|
|
|
// Apply refund counter, capped to half of the used gas.
|
|
uhalf := remaining.Div(self.gasUsed(), common.Big2)
|
|
refund := common.BigMin(uhalf, self.state.GetRefund())
|
|
self.gas.Add(self.gas, refund)
|
|
self.state.AddBalance(sender.Address(), refund.Mul(refund, self.gasPrice))
|
|
|
|
// Also return remaining gas to the block gas counter so it is
|
|
// available for the next transaction.
|
|
self.gp.AddGas(self.gas)
|
|
}
|
|
|
|
func (self *StateTransition) gasUsed() *big.Int {
|
|
return new(big.Int).Sub(self.initialGas, self.gas)
|
|
}
|
|
|
|
// callmsg wraps ethereum.CallMsg so it implements Message.
|
|
type callmsg struct {
|
|
ethereum.CallMsg
|
|
}
|
|
|
|
func (m callmsg) From() common.Address { return m.CallMsg.From }
|
|
func (m callmsg) Nonce() uint64 { return 0 }
|
|
func (m callmsg) CheckNonce() bool { return false }
|
|
func (m callmsg) To() *common.Address { return m.CallMsg.To }
|
|
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
|
|
func (m callmsg) Gas() *big.Int { return m.CallMsg.Gas }
|
|
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
|
|
func (m callmsg) Data() []byte { return m.CallMsg.Data }
|