账户:
以太坊中有两类账户,它们共用一个地址空间:外部账户由公钥-私钥对控制,也就是由人控制;合约账户由与账户一起存储的代码控制。交易:
交易是从一个账户发送到另一个账户的消息,它能包含二进制数据和以太币。这里的账户,可能是相同或特殊的零账户。二进制数据为合约负载。注意:
1.即使合约的代码不包含对selfdestruct的调用,它仍然可以通过delegatecall或者callcode执行自毁操作。
2.旧合约的删减有可能或不可能由以太坊客户端实现,另外,归档节点可能选择无限期地保存合约存储和代码。
3.当前的外部账户不可能从状态中删除。超过一般的算法,缺点是有一定的误识别率和删除困难。
参考网址
===========================================>20180504
go-ethereum v1.8.7
每个交易都带有两部分内容需要执行:
1.转账,由转出方地址向转入方地址转一笔以太币
2.携带[]byte类型成员变量payload,其每一个byte都对应一个单独虚拟机指令,这些内容都是由EVM对象来完成的,EVM结构体是以太坊虚拟机机制的核心,它与协同类的UML关系图如下所示:
1.Context结构体包含交易Transaction的信息GasPrice,GasLimit;区块Block的信息BlockNumber,Time,Difficulty以及转账函数等,这些信息都是提供给EVM的。
2.StateDB接口是针对state.StateDB结构体设计的本地行为接口,可以为EVM提供statedb的相关操作。
3.Interpreter结构体为解释器,用来解释执行EVM中合约的指令。
对EVM结构中定义的成员变量Context,仅声明变量而无类型,而变量名又是其类型名时,在Go语言中,这里意味着主结构体EVM可直接调用成员变量Context的所有方法和成员变量,比如EVM直接调用Context中的Transfer()
完成转账:
交易转账操作由Context对象中的TransferFunc类型函数实现,类似函数类型还有CanTransferFunc和GetHashFunc:
go-ethereum\core\vm:
type (
CanTransferFunc func(StateDB, common.Address, *big.Int) bool
TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
// GetHashFunc returns the nth block hash in the blockchain
// and is used by the BLOCKHASH EVM op code.
GetHashFunc func(uint64) common.Hash
)
go-ethereum\core\evm.go:
// NewEVMContext creates a new context for use in the EVM.
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {
// If we don't have an explicit author (i.e. not mining), extract from the header
var beneficiary common.Address
if author == nil {
beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation
} else {
beneficiary = *author
}
return vm.Context{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Origin: msg.From(),
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).Set(header.Time),
Difficulty: new(big.Int).Set(header.Difficulty),
GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()),
}
}
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
var cache map[uint64]common.Hash
return func(n uint64) common.Hash {
// If there's no hash cache yet, make one
if cache == nil {
cache = map[uint64]common.Hash{
ref.Number.Uint64() - 1: ref.ParentHash,
}
}
// Try to fulfill the request from the cache
if hash, ok := cache[n]; ok {
return hash
}
// Not cached, iterate the blocks and cache the hashes
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
cache[header.Number.Uint64()-1] = header.ParentHash
if n == header.Number.Uint64()-1 {
return header.ParentHash
}
}
return common.Hash{}
}
}
// CanTransfer checks wether there are enough funds in the address' account to make a transfer.
// This does not take the necessary gas in to account to make the transfer valid.
func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
return db.GetBalance(addr).Cmp(amount) >= 0
}
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
db.SubBalance(sender, amount)
db.AddBalance(recipient, amount)
}
这三个类型的函数变量CanTransfer,Transfer,GetHash,在Context初始化时从外部传入,目前使用的均是本地实现。
转账函数Transfer逻辑很简单,转出账户减去一笔以太币,转入账户加上一笔以太币。
由于EVM调用的Transfer函数是由Context提供的,因此假设基于以太坊平台开发, 需要设计一种全新的转账模式,那么只需要写一个新的Transfer()函数实现,在Context初始化时赋值即可。
这里转出和转入账户操作不会立即生效,StateDB不是真正的数据库,只是一个行为类似数据库的结构体,它在内部以Trie的数据结构来管理各个基于地址的账户,可理解为一个Cache。当该账户信息有变化时,先将变化存储在Trie中,仅当整个Block区块被插入到区块链中时,StateDB内缓存的所有账户的所有改动,才会被提交到真实的底层数据库。
合约的创建和赋值:
EVM是通过合约这个结构体来执行指令的,以下是合约的定义:
go-ethereum\core\vm\contract.go:
// ContractRef is a reference to the contract's backing object
type ContractRef interface {
Address() common.Address
}
// Contract represents an ethereum contract in the state database. It contains
// the the contract code, calling arguments. Contract implements ContractRef
type Contract struct {
// CallerAddress is the result of the caller which initialised this
// contract. However when the "call method" is delegated this value
// needs to be initialised to that of the caller's caller.
CallerAddress common.Address
caller ContractRef // 转账的转出方地址账户
self ContractRef // 转账的转入方地址账户
jumpdests destinations // result of JUMPDEST analysis.
Code []byte // 指令数组,每一个byte都对应一个预定义的虚拟机指令
CodeHash common.Hash // Code的RLP哈希值
CodeAddr *common.Address
Input []byte // 数据数组,是指令所操作的数据集合
Gas uint64
value *big.Int
Args []byte // 参数
DelegateCall bool
}
这里转入放地址被命名为self的原因,是Contract实现了ContractRef接口,返回的就是这个地址self:
// Address returns the contracts address
func (c *Contract) Address() common.Address {
return c.self.Address()
}
因此,当Contract对象以ContractRef接口出现时,它返回的地址就是它的self地址,在一个Contract A调用另一个Contract B时,A就会作为B的caller成员变量出现,此时Contract会被类型转换为ContractRef。
创建一个Contract对象时,重点关注对self的初始化,以及对Code,CodeAddr和Input的赋值。
另外StateDB提供方法SetCode可将指令数组Code存储在某个StateObject对象中;提供方法GetCode将从某个StateObject对象中读取已有的指令数组Code。
go-ethereum\core\state\Statedb.go:
func (self *StateDB) SetCode(addr common.Address, code []byte) {
stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetCode(crypto.Keccak256Hash(code), code)
}
}
func (self *StateDB) GetCode(addr common.Address) []byte {
stateObject := self.getStateObject(addr)
if stateObject != nil {
return stateObject.Code(self.db)
}
return nil
}
stateObject时以太坊内用来管理一个账户所有信息修改的结构体,它的唯一标识就是一个Address类型变量。
StateDB内部用一个巨大的map结构来管理这些stateObject对象,所有账户信息(包括以太币余额,指令数组Code,该账户发起合约次数nonce值等)发生的所有变化,都会首先缓存到StateDB内的某个stateObject中,而后在合适的时间内,被StateDB一起提交到底层数据库。一个Contract所对应的stateObject的地址就是Contract的self地址,也是转账的转入方地址。
// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
type StateDB struct {
db Database
trie Trie
// This map holds 'live' objects, which will get modified while processing a state transition.
stateObjects map[common.Address]*stateObject
stateObjectsDirty map[common.Address]struct{}
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// The refund counter, also used by state transitioning.
refund uint64
thash, bhash common.Hash
txIndex int
logs map[common.Hash][]*types.Log
logSize uint
preimages map[common.Hash][]byte
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
validRevisions []revision
nextRevisionId int
lock sync.Mutex
}
11
参考网页