Skip to content

Latest commit

 

History

History
483 lines (392 loc) · 20.4 KB

evm之实战.md

File metadata and controls

483 lines (392 loc) · 20.4 KB

简述

前面我们从一个智能合约的部署流程,到智能合约的字节码流程分析,再到evm虚拟机的源码分析。 整个分析其实就是为了移植虚拟机做基础。 如果看了前面几篇文章在来进行代码移植就会跟得心应手一些。

说明

因为涉及到的代码会比较多, 不可能把所有代码都列举出来。 所以也只是挑关键的部分进行讲解说明。 整个移植的代码我已经合到之前的那个简单(无用)demo版本的公链项目上了。 移植的以太坊版本为v1.8.12.

开始移植

首先先创建一个go的新项目, 将go-ethereum项目下core/vm文件夹下的代码全部拷贝到新项目下,我们为新的文件夹名称为cvm。 保存之后, 假设你用的是vscode(带上了go的周边插件)或者goland,这个时候你会发现有大量的报错。 没有关系, 因为很多以太坊包还没有被导入进来。 但是呢, 既然我们只想移植虚拟机部分,又不引入以太坊的其他模块。 这个时候我们就把需要的包直接拷贝到我们的项目中。 彻底分离和go-ethereum的关系。这里需要说明一下, 虽然是开源项目, 拷贝和使用别人的开源代码也要注意license的。

  1. 主要需要拷贝的包如下
  • go-ethereum/common这个文件夹, 我们也将其这个内容均拷贝到cvm这个文件夹下。
  • go-ethereum/params这个文件夹中的gas_tables.go, protocol_params.go两个文件拷贝到cvm/params文件夹下。
  • 创建log.go文件在cvm/types/下, 该文件中主要是智能合约emit提交事件时使用的log对象。 内容如下
type Log struct {
   // Consensus fields:
   // address of the contract that generated the event
   Address common.Address `json:"address" gencodec:"required"`
   // list of topics provided by the contract.
   Topics []common.Hash `json:"topics" gencodec:"required"`
   // supplied by the contract, usually ABI-encoded
   Data []byte `json:"data" gencodec:"required"`

   // Derived fields. These fields are filled in by the node
   // but not secured by consensus.
   // block in which the transaction was included
   BlockNumber uint64 `json:"blockNumber"`
   // hash of the transaction
   TxHash common.Hash `json:"transactionHash" gencodec:"required"`
   // index of the transaction in the block
   TxIndex uint `json:"transactionIndex" gencodec:"required"`
   // hash of the block in which the transaction was included
   BlockHash common.Hash `json:"blockHash"`
   // index of the log in the receipt
   Index uint `json:"logIndex" gencodec:"required"`

   // The Removed field is true if this log was reverted due to a chain reorganisation.
   // You must pay attention to this field if you receive logs through a filter query.
   Removed bool `json:"removed"`
}
  1. 在cvm目录下创建vm.go文件, 主要是生成evm的上下文对象。
// NewEVMContext creates a new context for use in the EVM.
func NewEVMContext(from common.Address, blockNum, timeStamp, difficulty int64) vm.Context {
	// If we don't have an explicit author (i.e. not mining), extract from the header
	return vm.Context{
		CanTransfer: CanTransfer,
		Transfer:    Transfer,
		GetHash:     GetHashFn(),
		Origin:      from,
		Coinbase:    common.Address{},
		BlockNumber: new(big.Int).Set(big.NewInt(blockNum)),
		Time:        new(big.Int).Set(big.NewInt(timeStamp)),
		Difficulty:  new(big.Int).Set(big.NewInt(difficulty)),
		GasLimit:    0xfffffffffffffff, //header.GasLimit,
		GasPrice:    new(big.Int).Set(big.NewInt(10)),
	}
}

// GetHashFn returns a GetHashFunc which retrieves header hashes by number 获取块号码对于的块hash
func GetHashFn() func(n 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)
}
  1. 接着我们把go-ethereum/account文件夹下的abi内容拷贝到cvm文件夹下。

abi/bind文件内容可以直接删除掉。此文件夹下是对智能合约进行函数调用进行编码的包。换句话调用智能合约构建的交易中的input内容就是需要此包中函数来生成的。当然如果你看了前面的文章,对智能合约调用了解的话,此处自然就理解这个包的作用了。

到了这里整个需要拷贝的文件就齐全了, 目录结构如下:

WechatIMG5.jpeg

  1. 接下来我们需要修改evm的部分代码了。

vm/contracts.go文件我们直接删除掉。 这个是自带的智能合约, 内部主要是一些内置函数, 注意实际使用的时候记得还是要实现的, evm.go文件中run函数忽略掉所有内置的合约函数。

WechatIMG6.jpeg

修改evm.go文件中的Call函数 当地址不存在时我们直接认为是创建地址, 忽略掉掉内置合约。 WechatIMG7.jpeg

  1. 接着我们要实现evm.StateDB接口的内容了, 因为此接口涉及涉及的主要是个账户状态相关的内容, 也即是说可整个区块的存储是有关联的, 暂时我也只能以一个示例来说明是如何简单的实现这些接口。

我们在cvm文件夹下创建一个account_state.go的文件。 定义的数据结构格式如下:

type accountObject struct {
	Address      common.Address              `json:"address,omitempty"`
	AddrHash     common.Hash                 `json:"addr_hash,omitempty"` // hash of ethereum address of the account
	ByteCode     []byte                      `json:"byte_code,omitempty"`
	Data         accountData                 `json:"data,omitempty"`
	CacheStorage map[common.Hash]common.Hash `json:"cache_storage,omitempty"` // 用于缓存存储的变量
}

type accountData struct {
	Nonce    uint64      `json:"nonce,omitempty"`
	Balance  *big.Int    `json:"balance,omitempty"`
	Root     common.Hash `json:"root,omitempty"` // merkle root of the storage trie
	CodeHash []byte      `json:"code_hash,omitempty"`
}

//AccountState 实现vm的StateDB的接口 用于进行测试
type AccountState struct {
	Accounts map[common.Address]*accountObject `json:"accounts,omitempty"`
}
  1. 接下来我们实现StateDB接口:
// CreateAccount 创建账户接口 
func (accSt *AccountState) CreateAccount(addr common.Address) {
	if accSt.getAccountObject(addr) != nil {
		return
	}
	obj := newAccountObject(addr, accountData{})
	accSt.setAccountObject(obj)
}

// SubBalance 减去某个账户的余额
func (accSt *AccountState) SubBalance(addr common.Address, amount *big.Int) {
	stateObject := accSt.getOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.SubBalance(amount)
	}
}

// AddBalance 增加某个账户的余额
func (accSt *AccountState) AddBalance(addr common.Address, amount *big.Int) {
	stateObject := accSt.getOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.AddBalance(amount)
	}
}

//// GetBalance 获取某个账户的余额
func (accSt *AccountState) GetBalance(addr common.Address) *big.Int {
	stateObject := accSt.getOrsetAccountObject(addr)
	if stateObject != nil {
		return stateObject.Balance()
	}
	return new(big.Int).SetInt64(0)
}
//GetNonce 获取nonce
func (accSt *AccountState) GetNonce(addr common.Address) uint64 {
	stateObject := accSt.getAccountObject(addr)
	if stateObject != nil {
		return stateObject.Nonce()
	}
	return 0
}

// SetNonce 设置nonce
func (accSt *AccountState) SetNonce(addr common.Address, nonce uint64) {
	stateObject := accSt.getOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.SetNonce(nonce)
	}
}

// GetCodeHash 获取代码的hash值
func (accSt *AccountState) GetCodeHash(addr common.Address) common.Hash {
	stateObject := accSt.getAccountObject(addr)
	if stateObject == nil {
		return common.Hash{}
	}
	return common.BytesToHash(stateObject.CodeHash())
}

//GetCode 获取智能合约的代码
func (accSt *AccountState) GetCode(addr common.Address) []byte {
	stateObject := accSt.getAccountObject(addr)
	if stateObject != nil {
		return stateObject.Code()
	}
	return nil
}

//SetCode 设置智能合约的code
func (accSt *AccountState) SetCode(addr common.Address, code []byte) {
	stateObject := accSt.getOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.SetCode(crypto.Sha256(code), code)
	}
}

// GetCodeSize 获取code的大小
func (accSt *AccountState) GetCodeSize(addr common.Address) int {
	stateObject := accSt.getAccountObject(addr)
	if stateObject == nil {
		return 0
	}
	if stateObject.ByteCode != nil {
		return len(stateObject.ByteCode)
	}
	return 0
}

// AddRefund 暂时先忽略补偿
func (accSt *AccountState) AddRefund(uint64) {
	return
}

//GetRefund ...
func (accSt *AccountState) GetRefund() uint64 {
	return 0
}

// GetState 和SetState 是用于保存合约执行时 存储的变量是否发生变化 evm对变量存储的改变消耗的gas是有区别的
func (accSt *AccountState) GetState(addr common.Address, key common.Hash) common.Hash {
	stateObject := accSt.getAccountObject(addr)
	if stateObject != nil {
		return stateObject.GetStorageState(key)
	}
	return common.Hash{}
}

// SetState 设置变量的状态
func (accSt *AccountState) SetState(addr common.Address, key common.Hash, value common.Hash) {
	stateObject := accSt.getOrsetAccountObject(addr)
	if stateObject != nil {
		fmt.Printf("SetState key: %x value: %s", key, new(big.Int).SetBytes(value[:]).String())
		stateObject.SetStorageState(key, value)
	}
}

// Suicide 暂时禁止自杀
func (accSt *AccountState) Suicide(common.Address) bool {
	return false
}

// HasSuicided ...
func (accSt *AccountState) HasSuicided(common.Address) bool {
	return false
}

// Exist 检查账户是否存在
func (accSt *AccountState) Exist(addr common.Address) bool {
	return accSt.getAccountObject(addr) != nil
}

//Empty 是否是空账户
func (accSt *AccountState) Empty(addr common.Address) bool {
	so := accSt.getAccountObject(addr)
	return so == nil || so.Empty()
}

// RevertToSnapshot ...
func (accSt *AccountState) RevertToSnapshot(int) {

}

// Snapshot ...
func (accSt *AccountState) Snapshot() int {
	return 0
}

// AddLog 添加事件触发日志
func (accSt *AccountState) AddLog(log *types.Log) {
	fmt.Printf("log: %v", log)
}

// AddPreimage 
func (accSt *AccountState) AddPreimage(common.Hash, []byte) {

}

// ForEachStorage  暂时没发现vm调用这个接口
func (accSt *AccountState) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) {

}

// Commit 进行持久存储 这里我们只将其简单的json话之后保存到本地磁盘中。
func (accSt *AccountState) Commit() error {
	// 将bincode写入文件
	file, err := os.Create("./account_sate.db")
	if err != nil {
		return err
	}
	err = json.NewEncoder(file).Encode(accSt)
	//fmt.Println("len(binCode): ", len(binCode), " code: ", binCode)
	// bufW := bufio.NewWriter(file)
	// bufW.Write(binCode)
	// // bufW.WriteByte('\n')
	// bufW.Flush()
	file.Close()
	return err
}

//TryLoadFromDisk  尝试从磁盘加载AccountState
func TryLoadFromDisk() (*AccountState, error) {
	file, err := os.Open("./account_sate.db")
	if err != nil && os.IsNotExist(err) {
		return NewAccountStateDb(), nil
	}
	if err != nil {
		return nil, err
	}

	// stat, _ := file.Stat()
	// // buf := stat.Size()
	var accStat AccountState

	err = json.NewDecoder(file).Decode(&accStat)
	return &accStat, err
}
  1. 接下来尝试部署两份智能合约进行测试:
pragma solidity ^0.4.21;
interface BaseInterface {
    function CurrentVersion() external view returns(string);
}

contract Helloworld {
    uint256 balance;
    event Triggle(address, string);
    mapping(address=>uint256) _mapamount; 
    
    constructor() public {
        balance = 6000000000;
        _mapamount[0] = 100;
        _mapamount[1] = 200;
    }
    
    function getbalance() public  returns (address, uint256) {
        emit Triggle(msg.sender, "funck");
        return (msg.sender, balance--);
    }
    
    function onlytest() public{
        _mapamount[1] = 100;
        emit Triggle(msg.sender, "onlytest");
    }
    
    function setBalance(uint256 tmp) public {
        balance = tmp;
    }
    
    function getVersion(address contractAddr) public view returns (string) {
        BaseInterface baseClass = BaseInterface(contractAddr);
       return baseClass.CurrentVersion();
    }
    
}

pragma solidity ^0.4.21;

contract BaseContract {
  address public owner;
 // 
 function CurrentVersion() pure public returns(string)  {
     return "BaseContractV0.1";
 }
}

通过这两个合约我们就可以测试到一些view 类型的函数调用, 一些对数据状态有修改的合约调用, 和跨合约的调用。 我们可以将上面的两个合约通过ethereum官方出品的remix进行编译,得到字节码。因为BaseContract没有涉及初始化的内容 所以我们可以直接使用runtime的bytecode。 不过我们直接使用Create函数去部署合约。

runtimeBytecode, contractAddr, leftgas, err := vmenv.Create(vm.AccountRef(normalAccount), helloCode, 10000000000, big.NewInt(0))

第一个返回值其实就是需要部署的runtime字节码 , 我们调用stateDb.SetCode(helloWorldcontactAccont, runtimeBytecode)将其部署。

var normalAddress, _ = hex.DecodeString("123456abc")
var hellWorldcontractAddress, _ = hex.DecodeString("987654321")
var baseContractAddress, _ = hex.DecodeString("038f160ad632409bfb18582241d9fd88c1a072ba")
var normalAccount = common.BytesToAddress(normalAddress)
var helloWorldcontactAccont = common.BytesToAddress(hellWorldcontractAddress)
var baseContractAccont = common.BytesToAddress(baseContractAddress)

// 基本账户字节码
var baseCodeStr = "608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632b225f29146100675780638afc3605146100f75780638da5cb5b1461010e578063f2fde38b14610165575b600080fd5b34801561007357600080fd5b5061007c6101a8565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100bc5780820151818401526020810190506100a1565b50505050905090810190601f1680156100e95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561010357600080fd5b5061010c6101e5565b005b34801561011a57600080fd5b50610123610227565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561017157600080fd5b506101a6600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061024c565b005b60606040805190810160405280601081526020017f42617365436f6e747261637456302e3100000000000000000000000000000000815250905090565b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102a757600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515156102e357600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3505600a165627a7a723058208c3064096245894122f6bcf5e2ee12e30d4775a3b8dca0b21f10d5a5bc386e8b0029"

// hellworld 账户字节码
var hellCodeStr = "6080604052600436106100615763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416634d9b3d5d81146100665780637e8800a7146100ab578063c3f82bc3146100c2578063fb1669ca14610165575b600080fd5b34801561007257600080fd5b5061007b61017d565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260208301919091528051918290030190f35b3480156100b757600080fd5b506100c06101fa565b005b3480156100ce57600080fd5b506100f073ffffffffffffffffffffffffffffffffffffffff6004351661028f565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012a578181015183820152602001610112565b50505050905090810190601f1680156101575780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561017157600080fd5b506100c0600435610389565b60408051338152602081018290526005818301527f66756e636b0000000000000000000000000000000000000000000000000000006060820152905160009182917f08c31d20d5c3a5f2cfe0adf83909e6411f43fe97eb091e15c12f3e5a203e8fde9181900360800190a150506000805460001981019091553391565b600080526001602090815260647fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb4955604080513381529182018190526008828201527f6f6e6c79746573740000000000000000000000000000000000000000000000006060830152517f08c31d20d5c3a5f2cfe0adf83909e6411f43fe97eb091e15c12f3e5a203e8fde9181900360800190a1565b606060008290508073ffffffffffffffffffffffffffffffffffffffff16632b225f296040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b1580156102fa57600080fd5b505af115801561030e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561033757600080fd5b81019080805164010000000081111561034f57600080fd5b8201602081018481111561036257600080fd5b815164010000000081118282018710171561037c57600080fd5b5090979650505050505050565b6000555600a165627a7a72305820c63a859d93a3512b52ccaec75bb9aa146648c41b21c8a0cd0cd2e2c1aede35ed0029"

var helloCode, _ = hex.DecodeString(hellCodeStr)
var baseCode, _ = hex.DecodeString(baseCodeStr)

func updateContract() {
	// 加载账户State
	stateDb, err := cvm.TryLoadFromDisk()
	if err != nil {
		panic(err)
	}
	stateDb.SetCode(helloWorldcontactAccont, helloCode)
	stateDb.SetCode(baseContractAccont, baseCode)
	fmt.Println(stateDb.Commit())
}

当我们调用一个智能合约比如getbalance函数, 代码类似下面这样:

// 4d9b3d5d : getbalance  7e8800a7: onlytest fb1669ca000000000000000000000000000000000000000000000000000000000000029a: setbalance 666
var input, _ = hex.DecodeString("7e8800a7")

func main() {
	// updateContract()
	// return
	// 创建账户State
	stateDb, err := cvm.TryLoadFromDisk()
	if err != nil {
		panic(err)
	}

	evmCtx := cvm.NewEVMContext(normalAccount, 100, 1200000, 1)
	vmenv := vm.NewEVM(evmCtx, stateDb, vm.Config{})

	ret, leftgas, err := vmenv.Call(vm.AccountRef(normalAccount), helloWorldcontactAccont, input, 1000000, big.NewInt(0))
	fmt.Printf("ret: %v, usedGas: %v, err: %v, len(ret): %v, hexret: %v, ", ret, 1000000-leftgas, err, len(ret), hex.EncodeToString(ret))

	abiObjet, _ := abi.JSON(strings.NewReader(hellWorldContractABIJson))

	// begin, length, _ := lengthPrefixPointsTo(0, ret)
	addr := new(common.Address)

	value := big.NewInt(0) //new(*big.Int)
	restult := []interface{}{addr, &value}
	fmt.Println(abiObjet.Unpack(&restult, "getbalance", ret))
	//fmt.Println(unpackAtomic(&restult, string(ret[begin:begin+length])))
	println(restult[0].(*common.Address).String(), (*restult[1].(**big.Int)).String())
	fmt.Println(stateDb.Commit())

}

最后

到了这里, evm移植流程就算完成了。 如果理解evm执行的原理, 大部分的工作其实就是拷贝, 出错的任务。 当然这个移植后的代码肯定是不能在生产中使用的, 但是需要修改和添加的代码主要也就是上文提到的内容。 最后还是想说明白原理和流程就是成功了一大半, 后面的部分主要就是调试和排错的过程了。