文章前言
区块链技术是近年来备受关注的技术之一,以太坊作为其中的佼佼者更是备受瞩目。以太坊不仅是一种数字货币,更是一种智能合约平台,它可以实现去中心化应用的开发,然而对于很多人来说以太坊仍然是一个陌生的概念,本文旨在通过对以太坊的基础概述让读者更好地了解以太坊的工作原理和应用场景,帮助读者更好地理解区块链技术的应用和发展
基本介绍
以太坊(Ethereum)是一个开源的区块链平台,它可以支持智能合约和去中心化应用(DApp)的开发和运行,它是由Vitalik Buterin于2013年提出并于2014年在众筹中筹集了超过1800万美元的资金,以太坊正式发布于2015年,以太坊的核心是以太坊虚拟机(Ethereum Virtual Machine,EVM),它是一个基于栈的虚拟机,可以执行智能合约的代码,以太坊的智能合约使用Solidity编写,它是一种基于面向对象编程语言的智能合约语言,类似于JavaScript,而以太坊的加密货币是以太币(Ether,简称ETH)则是以太坊网络中的主要货币,也是智能合约的执行代币,以太币可以在加密货币交易所中进行交易也可以作为支付费用用于执行智能合约,以太坊的去中心化应用可以通过以太坊的智能合约进行开发和部署,这些应用可以涵盖各种领域,例如:数字身份、金融服务、游戏、社交网络等,以太坊的去中心化应用具有去中心化、透明、安全、不可篡改等优点,可以解决中心化应用存在的安全、信任和隐私问题
技术架构
以太坊是一个去中心化的分布式计算平台,其技术架构主要包括以下几个组件:
-
以太坊客户端:以太坊客户端是运行在节点上的软件程序,用于连接以太坊网络,以太坊客户端可以是全节点或轻节点,全节点需要下载并存储整个区块链数据,而轻节点可以通过其他节点获取所需数据
-
智能合约:智能合约是运行在以太坊虚拟机上的程序代码,用于实现各种业务逻辑和数据管理等功能,智能合约可以通过Solidity等编程语言编写并通过以太坊网络进行部署和调用
-
DApps:DApps(去中心化应用程序)是基于以太坊平台开发的应用程序,可以实现各种功能,例如:数字货币钱包、投票系统、游戏等
-
以太坊网络:以太坊网络是一个去中心化的分布式网络,由节点组成,每个节点都可以相互通信和交换信息,以太坊网络采用P2P网络协议来实现数据传输和节点之间的通信
-
区块链:以太坊的区块链是一个去中心化的分布式数据库,包含了所有的交易和智能合约代码,区块链采用哈希链结构来存储数据并使用共识算法来确保区块链的安全和有效性
以下是一个简单的以太坊技术架构示意图:
+-------------------------------------------------------------------------+
| |
| DApps |
| |
+-------------------------------------------------------------------------+
| |
| 智能合约虚拟机 |
| |
+-------------------------------------------------------------------------+
| |
| 以太坊客户端 |
| |
+-------------------------------------------------------------------------+
| |
| 以太坊网络 |
| |
+-------------------------------------------------------------------------+
| |
| 区块链 |
| |
+-------------------------------------------------------------------------+
在以太坊技术架构中DApps是建立在智能合约之上的应用程序,智能合约使用Solidity等编程语言编写,运行在以太坊虚拟机上,以太坊客户端是连接到以太坊网络的软件,可以是全节点或轻节点,用于同步区块链数据和处理交易,以太坊网络是由节点组成的分布式网络,节点之间可以相互通信和交换信息,区块链是一个去中心化的分布式数据库,包含了所有的交易和智能合约代码
共识算法
以太坊是一个基于区块链技术的分布式计算平台,它使用共识算法来保证交易的真实性和安全性,以下是对以太坊共识算法各个阶段的实例和共识算法实例代码的详细介绍:
工作量证明
以太坊最初使用的共识算法是基于工作量证明的算法,在PoW算法中矿工需要通过计算随机数的方式来竞争获得打包区块的权利,这个过程需要消耗计算资源和电力,因此获胜的矿工会获得区块奖励和交易手续费,这也是PoW算法的激励机制,以下是以太坊PoW算法的实例代码,其中我们定义了两个函数ethash和ethashTwo,这是以太坊的Ethash算法的一部分,在Ethash算法中需要计算出一个\\”pow_hash\\”,这是通过对随机数和种子哈希进行计算得出的,在以太坊中需要对\\”pow_hash\\”进行两次哈希运算以获得最终的哈希值,因此ethash函数计算出第一次哈希值,ethashTwo函数对其进行第二次哈希运算
import (
\\\"crypto/sha256\\\"
\\\"encoding/hex\\\"
\\\"fmt\\\"
)
func ethash(seedHash string, nonce int) string {
powHash := sha256.Sum256([]byte(seedHash + string(nonce)))
return hex.EncodeToString(powHash[:])
}
// 以太坊的Ethash算法需要进行两次哈希运算
func ethashTwo(seedHash string, nonce int) string {
powHash := ethash(seedHash, nonce)
powHash = sha256.Sum256([]byte(powHash))
return hex.EncodeToString(powHash[:])
}
权益证明算法
PoW算法存在一些缺点,比如能源消耗大、无法避免51%攻击等,因此以太坊开始转向权益证明的共识算法,在PoS算法中每个持币者都可以通过持有一定数量的数字货币来参与验证交易和打包区块,以下是以太坊PoS算法的实例代码,再这里我们定义了一个Validator结构体和一个PoS结构体,Validator结构体表示持币者,包括一个balance属性,表示该持币者拥有的数字货币数量,PoS结构体表示整个权益证明算法,包括所有持币者的列表和总数字货币数量等信息,在此算法中可以通过getValidator函数获取指定索引(即随机数)对应的持币者,通过getWeight函数获取该持币者所占的权重以及通过getRandomValidator和getRandomWeightedValidator函数获取随机的持币者和其权重
type Validator struct {
balance int
}
type PoS struct {
validators []*Validator
totalBalance int
}
func (p *PoS) getValidator(index int) *Validator {
return p.validators[index%len(p.validators)]
}
func (p *PoS) getWeight(validator *Validator) float64 {
return float64(validator.balance) / float64(p.totalBalance)
}
func (p *PoS) getRandomValidator(seed int) *Validator {
index := int(sha256.Sum256([]byte(string(seed))))[0]
return p.getValidator(int(index))
}
func (p *PoS) getRandomWeightedValidator(seed int) (*Validator, float64) {
index := int(sha256.Sum256([]byte(string(seed))))[0] % len(p.validators)
validator := p.getValidator(index)
weight := p.getWeight(validator)
return validator, weight
}
混合共识算法
除了PoW和PoS算法,以太坊还在研究和实验其他共识算法,比如:混合共识算法,混合共识算法将PoW和PoS算法结合起来,既能够保证区块链的安全性又能够提高网络的可扩展性,以下是以太坊混合共识算法的实例代码,在这里我们定义了一个Hybrid结构体来表示混合共识算法,与PoS算法类似该算法也包括持币者列表和数字货币数量等信息,但与PoS算法不同的是混合共识算法将持币者分为PoW验证者和PoS验证者两类,在此算法中可以通过isPowValidator函数判断指定持币者是否为PoW验证者,通过getValidator和getWeight函数获取持币者和其权重,通过getRandomValidator和getRandomWeightedValidator函数获取随机的持币者和其权重
type Hybrid struct {
threshold int
powValidators []*Validator
posValidators []*Validator
powTotal int
posTotal int
}
func (h *Hybrid) getValidator(index int) *Validator {
if index < h.threshold {
return h.powValidators[index%len(h.powValidators)]
} else {
return h.posValidators[index%len(h.posValidators)]
}
}
func (h *Hybrid) getWeight(validator *Validator) float64 {
if h.isPowValidator(validator) {
return float64(validator.balance) / float64(h.powTotal)
} else {
return float64(validator.balance) / float64(h.posTotal)
}
}
func (h *Hybrid) getRandomValidator(seed int) *Validator {
index := int(sha256.Sum256([]byte(string(seed))))[0]
return h.getValidator(int(index))
}
func (h *Hybrid) getRandomWeightedValidator(seed int) (*Validator, float64) {
index := int(sha256.Sum256([]byte(string(seed))))[0] % (len(h.powValidators) + len(h.posValidators))
validator := h.getValidator(index)
weight := h.getWeight(validator)
return validator, weight
}
func (h *Hybrid) isPowValidator(validator *Validator) bool {
for _, v := range h.powValidators {
if v == validator {
return true
}
}
return false
}
账户介绍
以太坊账号是以太坊网络中的一种身份标识,可以用于存储以太币(ETH)和以及执行智能合约,以太坊账号有两种类型:外部账号(Externally Owned Account,EOA)和合约账号(Contract Account)
外部账户
外部账户是以太坊中最基本的账户类型,它们与普通的银行账户类似,可以接收和发送以太币和其他数字资产,每个外部账户都由一个私钥和一个公钥组成,私钥用于签署交易,公钥用于验证签名,以太坊中的外部账户可以由个人或组织拥有和控制,它们不依赖于智能合约或其他特殊机制,在以太坊中创建外部账户需要以下步骤:
A、生成新的私钥和公钥对
首先我们需要使用Go语言生成一个新的私钥和公钥对,我们可以使用crypto和hex包提供的函数和方法来生成一个新的私钥和对应的公钥并将它们转换为十六进制字符串表示,在下面的代码中我们使用了crypto和hex包提供的函数和方法生成了一个新的私钥和对应的公钥并将它们转换为十六进制字符串表示
import (
\\\"crypto/ecdsa\\\"
\\\"crypto/rand\\\"
\\\"encoding/hex\\\"
\\\"fmt\\\"
\\\"github.com/ethereum/go-ethereum/crypto\\\"
)
// 生成以太坊外部账户的函数
func generateExternalAccount() (string, string) {
// 使用ECDSA算法生成一个新的私钥
privateKey, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
// 从私钥中派生公钥
publicKey := privateKey.PublicKey
// 将私钥转换为字节数组,并编码为十六进制字符串
privateKeyBytes := privateKey.D.Bytes()
privateKeyHex := hex.EncodeToString(privateKeyBytes)
// 将公钥转换为字节数组,并编码为十六进制字符串
publicKeyBytes := crypto.FromECDSAPub(&publicKey)
publicKeyHex := hex.EncodeToString(publicKeyBytes)
// 返回私钥和公钥的十六进制字符串表示
return privateKeyHex, publicKeyHex
}
B、将公钥哈希为一个以太坊地址
接下来我们需要将公钥哈希为一个以太坊地址,可以使用common包提供的函数和方法将公钥转换为地址,在下面的代码中,我们使用了common和crypto包提供的函数和方法将公钥转换为以太坊地址,即首先将公钥的十六进制字符串解码为字节数组,然后计算公钥的Keccak-256哈希值,最后我们将哈希值的后20个字节作为地址并将其转换为十六进制字符串表示
import (
\\\"github.com/ethereum/go-ethereum/common\\\"
\\\"github.com/ethereum/go-ethereum/crypto\\\"
)
// 将公钥转换为以太坊地址的函数
func publicKeyToAddress(publicKeyHex string) string {
// 将公钥的十六进制字符串解码为字节数组
publicKeyBytes, _ := hex.DecodeString(publicKeyHex)
// 计算公钥的Keccak-256哈希值
hash := crypto.Keccak256(publicKeyBytes[1:])
// 取哈希值的后20个字节,作为地址
address := common.BytesToAddress(hash[12:])
// 返回地址的十六进制字符串表示
return address.Hex()
}
C、在以太坊网络中广播一个特殊的交易将一定数量的以太币发送到新生成的地址
最后我们需要在以太坊网络中广播一个特殊的交易并将一定数量的以太币发送到新生成的地址,在这里我们可以使用ethclient和types包提供的函数和方法来创建一个新交易并将其签名,之后将其发送到以太坊网络中,以下是完整的代码示例:
import (
\\\"context\\\"
\\\"crypto/ecdsa\\\"
\\\"crypto/rand\\\"
\\\"encoding/hex\\\"
\\\"fmt\\\"
\\\"github.com/ethereum/go-ethereum/common\\\"
\\\"github.com/ethereum/go-ethereum/core/types\\\"
\\\"github.com/ethereum/go-ethereum/crypto\\\"
\\\"github.com/ethereum/go-ethereum/ethclient\\\"
\\\"math/big\\\"
)
// 生成以太坊外部账户的函数
func generateExternalAccount() (string, string, string) {
// 使用ECDSA算法生成一个新的私钥
privateKey, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
// 从私钥中派生公钥
publicKey := privateKey.PublicKey
// 将私钥转换为字节数组,并编码为十六进制字符串
privateKeyBytes := privateKey.D.Bytes()
privateKeyHex := hex.EncodeToString(privateKeyBytes)
// 将公钥转换为字节数组,并编码为十六进制字符串
publicKeyBytes := crypto.FromECDSAPub(&publicKey)
publicKeyHex := hex.EncodeToString(publicKeyBytes)
// 将公钥哈希为以太坊地址
address := publicKeyToAddress(publicKeyHex)
// 返回私钥、公钥和地址的十六进制字符串表示
return privateKeyHex, publicKeyHex, address
}
// 将公钥转换为以太坊地址的函数
func publicKeyToAddress(publicKeyHex string) string {
// 将公钥的十六进制字符串解码为字节数组
publicKeyBytes, _ := hex.DecodeString(publicKeyHex)
// 计算公钥的Keccak-256哈希值
hash := crypto.Keccak256(publicKeyBytes[1:])
// 取哈希值的后20个字节,作为地址
address := common.BytesToAddress(hash[12:])
// 返回地址的十六进制字符串表示
return address.Hex()
}
// 发送以太币到指定地址的函数
func sendEtherToAddress(client *ethclient.Client, privateKeyHex string, toAddress string, value *big.Int) error {
// 将私钥的十六进制字符串解码为字节数组
privateKeyBytes, _ := hex.DecodeString(privateKeyHex)
// 使用私钥创建一个新的账户
privateKeyECDSA, _ := crypto.ToECDSA(privateKeyBytes)
// 查询账户的nonce值
nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(privateKeyECDSA.PublicKey))
if err != nil {
return err
}
// 创建一个新的转账交易
tx := types.NewTransaction(nonce, common.HexToAddress(toAddress), value, 21000, big.NewInt(1000000000), nil)
// 对交易进行签名
signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKeyECDSA)
if err != nil {
return err
}
// 将交易发送到以太坊网络
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
return err
}
return nil
}
合约账户
以太坊合约账户是以太坊区块链上的另一种账户类型,合约账户是由智能合约创建的,它可以执行任意的计算和操作,合约账户不依赖于个人或组织,而是由代码和规则控制,与外部账户不同的是合约账户没有私钥和公钥,它们使用代码执行交易而不是签署交易,在以太坊中创建合约账户需要以下步骤:
A、编写智能合约代码
首先需要编写智能合约代码,我们可以使用Solidity编程语言编写智能合约代码,下面是一个简单的Solidity智能合约示例:在这里我们定义了一个名为SimpleStorage的智能合约,包含一个storedData变量和两个函数set和get,set函数用于设置storedData变量的值,get函数用于返回storedData变量的值
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
B、使用Solidity编译器将智能合约代码编译为字节码
接下来,我们需要使用Solidity编译器将智能合约代码编译为字节码,我们可以使用solc命令行工具或solc Go语言包提供的函数和方法将Solidity源代码编译为字节码,以下是使用solc命令行工具编译Solidity源代码的示例:
$ solc --bin SimpleStorage.sol
上面我们使用solc命令行工具编译名为SimpleStorage.sol的Solidity源代码文件并输出合约字节码,下面则是使用solc Go语言包编译Solidity源代码的示例,可以看到我们首先使用了solidity包提供的函数和方法创建了一个Solidity编译器并使用它将Solidity源代码编译为字节码,具体地即为首先创建一个Solidity编译器,然后调用编译器的Compile方法将Solidity源代码作为参数传入并获取编译结果,最后返回合约字节码
import (
\\\"fmt\\\"
\\\"github.com/ethereum/go-ethereum/accounts/abi/bind\\\"
\\\"github.com/ethereum/go-ethereum/common\\\"
\\\"github.com/ethereum/go-ethereum/core/types\\\"
\\\"github.com/ethereum/go-ethereum/crypto\\\"
\\\"github.com/ethereum/go-ethereum/ethclient\\\"
\\\"github.com/ethereum/go-ethereum/params\\\"
\\\"github.com/ethereum/go-ethereum/solidity\\\"
\\\"io/ioutil\\\"
\\\"math/big\\\"
)
// 编译Solidity源代码的函数
func compileSolidity(source string) ([]byte, error) {
// 创建一个Solidity编译器
compiler := solidity.NewCompiler()
// 编译Solidity源代码
compiled, err := compiler.Compile(source)
if err != nil {
return nil, err
}
// 返回合约字节码
return compiled[\\\"SimpleStorage\\\"].Bin, nil
}
C、在以太坊网络中广播一个特殊的交易,将合约字节码部署到新生成的地址
最后我们需要在以太坊网络中广播一个特殊的交易将合约字节码部署到新生成的地址,我们可以使用ethclient和types包提供的函数和方法,创建一个新交易并将其签名并将其发送到以太坊网络中
以下是完整的代码示例:
import (
\\\"context\\\"
\\\"crypto/ecdsa\\\"
\\\"crypto/rand\\\"
\\\"encoding/hex\\\"
\\\"fmt\\\"
\\\"github.com/ethereum/go-ethereum/accounts/abi/bind\\\"
\\\"github.com/ethereum/go-ethereum/common\\\"
\\\"github.com/ethereum/go-ethereum/core/types\\\"
\\\"github.com/ethereum/go-ethereum/crypto\\\"
\\\"github.com/ethereum/go-ethereum/ethclient\\\"
\\\"github.com/ethereum/go-ethereum/params\\\"
\\\"github.com/ethereum/go-ethereum/solidity\\\"
\\\"io/ioutil\\\"
\\\"math/big\\\"
)
// 创建以太坊合约账户的函数
func createContractAccount(client *ethclient.Client, privateKeyHex string, bytecode []byte) (string, error) {
// 将私钥的十六进制字符串解码为字节数组
privateKeyBytes, _ := hex.DecodeString(privateKeyHex)
// 使用私钥创建一个新的账户
privateKeyECDSA, _ := crypto.ToECDSA(privateKeyBytes)
// 查询账户的nonce值
nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(privateKeyECDSA.PublicKey))
if err != nil {
return \\\"\\\", err
}
// 创建一个新的合约部署交易
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
return \\\"\\\", err
}
// 设置交易的gas limit
gasLimit := uint64(3000000)
// 创建一个新的合约部署交易
contractTx := types.NewContractCreation(nonce, big.NewInt(0), gasLimit, gasPrice, bytecode)
// 为交易签名
chainID, _ := client.NetworkID(context.Background())
signedTx, err := types.SignTx(contractTx, types.NewEIP155Signer(chainID), privateKeyECDSA)
if err != nil {
return \\\"\\\", err
}
// 发送交易到以太坊网络
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
return \\\"\\\", err
}
// 等待交易被打包
receipt, err := bind.WaitMined(context.Background(), client, signedTx)
if err != nil {
return \\\"\\\", err
}
// 返回合约账户地址
return receipt.ContractAddress.String(), nil
}
func main() {
// 连接到以太坊节点
client, err := ethclient.Dial(\\\"http://localhost:8545\\\")
if err != nil {
fmt.Println(err)
return
}
// 读取Solidity源代码
source, err := ioutil.ReadFile(\\\"SimpleStorage.sol\\\")
if err != nil {
fmt.Println(err)
return
}
// 编译Solidity源代码
bytecode, err := compileSolidity(string(source))
if err != nil {
fmt.Println(err)
return
}
// 创建一个新的合约账户
privateKeyHex := \\\"0123456789012345678901234567890123456789012345678901234567890123\\\"
contractAddress, err := createContractAccount(client, privateKeyHex, bytecode)
if err != nil {
fmt.Println(err)
return
}
// 输出合约账户地址
fmt.Println(\\\"Contract address:\\\", contractAddress)
}
在上面的代码中我们首先连接到了以太坊节点,然后读取了Solidity源代码并使用compileSolidity函数将其编译为合约字节码,接下来使用createContractAccount函数创建了一个新的合约账户并将合约字节码部署到该账户地址,最后输出了合约账户地址
差异对比
合约账户和外部账户之间有以下区别:
合约账户:
-
合约账户是由智能合约创建的,没有对应的私钥
-
合约账户有自己的代码和存储空间,可以执行智能合约中定义的函数
-
合约账户可以接收和发送以太币和其他代币,也可以与其他合约账户交互
-
合约账户的状态存储在以太坊的全节点中,可以被所有网络参与者访问和调用
-
合约账户的创建需要消耗一定的以太币作为手续费
外部账户:
-
外部账户由私钥和地址对创建,可以使用私钥对交易进行签名
-
外部账户没有代码和存储空间,只能进行简单的以太币和其他代币的接收和发送
-
外部账户的状态存储在本地钱包中,只有拥有私钥的用户可以访问和使用
-
外部账户可以通过交易与合约账户进行交互,但无法执行合约中的函数
-
外部账户的创建不需要消耗以太币,但交易需要支付一定的手续费
交易概览
在以太坊中交易是指一条用于在账户之间转移资产或触发智能合约的指令,交易也是以太坊中最基本的操作单位,它包含了资产转移、智能合约调用等多种操作,交易由以下几个组成部分构成:
-
非交易数据(Non-transaction data):这是一个可选的字段,用于存储任意数据,可以是任何格式的数据,例如:文本、图片等,在以太坊中这个字段通常用于传递消息或者数据,而不是资产转移
-
发送方地址(Sender address):这是交易的发送方地址,也称为交易的签名者地址,发送方地址是一个以太坊账户的公钥地址,代表了交易的发起者
-
接收方地址(Recipient address):这是交易的接收方地址,也称为交易的目标地址,接收方地址通常是一个以太坊账户地址,也可以是一个智能合约地址
-
转移的资产数量(Value):这是交易中转移的资产数量,通常是以太币(Ether),资产数量是一个整数,以wei为单位,1 Ether = 10^18 wei
-
数据(Data):这是一个可选的字段,用于在交易中传递数据。如果接收方地址是一个智能合约地址,那么数据字段通常包含调用智能合约的函数和参数
-
签名(Signature):这是交易的签名,用于验证交易发送方的身份和授权,交易签名由发送方地址和发送方账户的私钥生成
除此之外,还有以下几个字段:
-
Gas价格(Gas price):这是发送方愿意支付的每单位gas的价格,Gas是以太坊中执行操作的计量单位,每个操作都需要消耗一定的gas,Gas价格越高,交易被打包的速度越快
-
Gas限制(Gas limit):这是发送方愿意为这个交易设置的gas限制,Gas限制是交易执行所需的最大gas数量,如果交易执行完毕时消耗的gas数量超出了gas限制,交易将被回滚
-
随机数(Nonce):这是一个用于避免交易重放的随机数,Nonce是一个整数,每次交易都必须使用一个不同的Nonce值
下面是一个以太坊交易的示例:
{
\\\"nonce\\\": \\\"0x01\\\",
\\\"gasPrice\\\": \\\"0x12A05F200\\\",
\\\"gasLimit\\\": \\\"0x4C4B40\\\",
\\\"to\\\": \\\"0x21a31ea9e1af03f1c3ebc99b6a0afb9c1a3a0dcb\\\",
\\\"value\\\": \\\"0x1\\\",
\\\"data\\\": \\\"0x\\\",
\\\"v\\\": \\\"0x1c\\\",
\\\"r\\\": \\\"0xc0b8d7e9c5c1e3d3d1b6a785d9d5d2e9f7c0b41a33c37c7f28e4cc26b4869e9b\\\",
\\\"s\\\": \\\"0x4e5c0f0eab3e9a4c7a4cf2e7c9f6f4c0d4b7d9a02c4e2e5e2a9d6b2a56d0f9e2\\\"
}
消息概览
在以太坊中消息是一种与交易类似的指令,它主要用于在智能合约之间传递数据,消息与交易不同的是它没有资产转移的功能,消息由以下几个组成部分构成
-
发送方地址(Sender address):这是消息的发送方地址,也称为消息的签名者地址,发送方地址是一个以太坊账户的公钥地址,代表了消息的发起者
-
接收方地址(Recipient address):这是消息的接收方地址,也称为消息的目标地址,接收方地址通常是一个智能合约地址
-
数据(Data):这是消息中传递的数据,通常包含调用智能合约的函数和参数
-
Gas限制(Gas limit):这是发送方愿意为这个消息设置的gas限制,Gas限制是消息执行所需的最大gas数量,如果消息执行完毕时消耗的gas数量超出了gas限制,消息将被回滚
下面是一个以太坊消息的示例:
{
\\\"from\\\": \\\"0x3F5CBA93BdF3eC13b6b4F6E8470AabD60a4c2D5a\\\",
\\\"to\\\": \\\"0xa7d7b20c31be8f8a3d826ed8b199bfaa9c72761d\\\",
\\\"data\\\": \\\"0x6d4ce63c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a\\\",
\\\"gas\\\": \\\"0x30d40\\\"
}
费用设计
在以太坊中Gas是一种计量单位,它主要用于衡量智能合约执行所需的计算量和存储空间,每个操作都需要消耗一定的Gas,例如:进行算术运算、存储数据、调用其他合约等等。为了防止恶意合约无限制消耗计算资源,以太坊引入了Gas限制和Gas价格的概念,Gas价格是发送方愿意支付的每单位Gas的价格,而Gas限制是交易或消息执行所需的最大Gas数量
以太坊中的Gas费用是由Gas价格和Gas限制共同决定的,Gas费用的计算公式如下,如果交易或消息执行的实际Gas消耗量小于Gas限制,那么发送方只需要支付实际消耗的Gas数量与Gas价格的乘积,如果实际消耗量超过了Gas限制,交易或消息将被回滚,发送方需要支付Gas限制与Gas价格的乘积
Gas费用 = Gas价格 * Gas限制
以太坊中的Gas费用主要有以下几个作用
-
防止恶意合约占用过多计算资源,由于每个操作都需要消耗一定的Gas,合约的执行时间和计算复杂度都受到限制,发送方需要支付一定的Gas费用以保证合约的执行能够得到适当的限制
-
鼓励矿工打包Gas价格高的交易,矿工可以选择要打包的交易,而高Gas价格的交易会被矿工优先选择打包,因为它们能够获得更高的手续费
-
鼓励合约编写者优化合约性能,合约编写者需要考虑Gas的消耗以保证合约的执行效率和成本
下面是一个以太坊交易的示例,其中包含了Gas价格和Gas限制:
{
\\\"nonce\\\": \\\"0x01\\\",
\\\"gasPrice\\\": \\\"0x12A05F200\\\",
\\\"gasLimit\\\": \\\"0x4C4B40\\\",
\\\"to\\\": \\\"0x21a31ea9e1af03f1c3ebc99b6a0afb9c1a3a0dcb\\\",
\\\"value\\\": \\\"0x1\\\",
\\\"data\\\": \\\"0x\\\",
\\\"v\\\": \\\"0x1c\\\",
\\\"r\\\": \\\"0xc0b8d7e9c5c1e3d3d1b6a785d9d5d2e9f7c0b41a33c37c7f28e4cc26b4869e9b\\\",
\\\"s\\\": \\\"0x4e5c0f0eab3e9a4c7a4cf2e7c9f6f4c0d4b7d9a02c4e2e5e2a9d6b2a56d0f9e2\\\"
}
在这个示例中Gas价格为0x12A05F200,表示发送方愿意支付每单位Gas的价格为5000000000 Gwei,Gas限制为0x4C4B40,表示交易执行所需的最大Gas数量为500000,因此Gas费用为0x12A05F200 * 0x4C4B40 = 0x5F5E10000000 wei,约等于0.05 Ether,发送方需要支付这个费用以保证交易能够被打包并执行
智能合约
基本介绍
智能合约是一种在区块链上运行的自动化合约,它是一段以编程语言编写的代码,可以在区块链上执行特定的操作,智能合约可以在没有中间人的情况下执行交易、管理数字资产、验证身份、执行投票等各种业务逻辑,从而实现去中心化的应用程序,智能合约的执行结果不可篡改,具有高度的可信度和安全性。
智能合约通常包括以下几个部分:
-
状态变量(State variables):这是智能合约的数据存储区域,用于存储合约的状态信息,状态变量可以是各种数据类型,例如:整数、字符串、数组等
-
函数(Functions):这是智能合约的代码执行逻辑,可以用于修改合约的状态、触发事件、调用其他合约等操作
-
事件(Events):这是智能合约中定义的用于通知外部应用程序的事件,例如:交易成功、状态变化等
-
修饰符(Modifiers):这是智能合约中用于修饰函数的关键字,用于限制函数的访问权限、检查输入参数等
以下是一个简单的以太坊智能合约示例,它模拟了一个简单的投票系统来记录每个参与者对不同候选人的投票情况:
pragma solidity ^0.8.0;
contract Voting {
// 定义候选人结构体
struct Candidate {
string name;
uint voteCount;
}
// 定义候选人数组
Candidate[] public candidates;
// 定义投票人地址到投票状态的映射
mapping(address => bool) public voters;
// 添加候选人
function addCandidate(string memory _name) public {
candidates.push(Candidate({name: _name, voteCount: 0}));
}
// 进行投票
function vote(uint _candidateIndex) public {
require(_candidateIndex < candidates.length, \\\"Invalid candidate index\\\");
require(!voters[msg.sender], \\\"You have already voted\\\");
// 更新候选人的票数
candidates[_candidateIndex].voteCount++;
// 标记当前地址已经投票
voters[msg.sender] = true;
}
// 获取候选人的票数
function getVoteCount(uint _candidateIndex) public view returns (uint) {
require(_candidateIndex < candidates.length, \\\"Invalid candidate index\\\");
return candidates[_candidateIndex].voteCount;
}
}
在这个示例中智能合约定义了一个候选人结构体,包含了候选人的姓名和票数,候选人数据以数组的形式存储,可以通过addCandidate函数添加候选人,投票过程中每个参与者可以通过vote函数进行投票,投票结果会更新候选人的票数并且标记该参与者已经投票,getVoteCount 函数用于获取指定候选人的票数
生命周期
智能合约的生命周期主要包括以下几个阶段:
-
编写:智能合约的生命周期从编写智能合约代码开始,在这个阶段合约编写者需要确定合约的业务逻辑、数据结构等并使用智能合约编程语言编写代码
-
部署:智能合约部署是将合约代码上传到区块链上并创建智能合约实例的过程,在部署过程中需要指定合约的构造函数参数、Gas价格和Gas限制等信息并支付一定的手续费
-
调用:智能合约调用是指在区块链上执行合约代码的过程,合约可以通过交易或消息的方式被调用,交易需要包含转移的资产数量,而消息则不需要,在调用过程中需要支付一定的Gas费用
-
更新:智能合约可能需要进行更新,例如修复漏洞、升级合约功能等,在更新合约时需要部署新的合约并将旧合约中的数据迁移到新合约中
-
终止:智能合约的生命周期可能在任何时候结束,例如:合约不再需要、合约存在漏洞等,在这种情况下合约可以被销毁将其从区块链上删除
下面是一个智能合约生命周期的示意图:
+-----------------+
| 编写合约 |
+-----------------+
|
v
+-----------------+
| 部署合约 |
+-----------------+
|
v
+-----------------+
| 调用合约 |
+-----------------+
|
v
+-----------------+
| 更新合约 |
+-----------------+
|
v
+-----------------+
| 终止合约 |
+-----------------+
挖矿实践
以太坊2.0版本中采用了权益证明(PoS)机制来替代工作量证明机制,成为了以太坊的新共识算法,在PoS机制下矿工需要拥有一定的以太坊数量作为权益来参与网络验证和出块,以下是以太坊PoS挖矿过程的详细介绍:
-
抵押:在PoS机制下矿工需要抵押一定数量的以太坊作为权益来参与网络验证和出块,这些抵押的以太坊将被锁定在智能合约中直到矿工退出网络或被罚没为止
-
验证:矿工需要验证网络上的交易和区块并将验证结果广播到网络中,验证的过程包括验证交易的签名、检查交易的合法性、验证区块的哈希值等
-
出块:在PoS机制下矿工不再需要进行复杂的数学问题计算,而是通过出块权益来获得出块的机会,矿工的出块权益与其抵押的以太坊数量成正比,出块权益越高的矿工,出块的机会就越高
-
获得奖励:矿工在出块时会获得一定的出块奖励和交易手续费,在PoS机制下,出块奖励和交易手续费将被分配给出块矿工和验证者
以下是一个简单的以太坊PoS挖矿示例,假设矿工拥有抵押了100个以太坊,需要参与出块和验证的过程:
// 抵押以太坊作为权益
depositEther(100 ether)
while True:
// 获取待验证的交易和区块
transactions, block = getTransactionsAndBlock()
// 验证交易和区块
if verifyTransactions(transactions) and verifyBlock(block):
// 出块
if getRandomNumber() < getBlockChance():
newBlock = createNewBlock(transactions, block)
broadcast(newBlock)
collectBlockReward(newBlock)
在这个示例中矿工首先抵押了100个以太坊作为权益,然后不断地获取待验证的交易和区块并进行验证,如果验证通过,矿工将根据其出块权益计算出块的机会并在随机数小于出块机会的情况下创建新区块并广播到网络中,最后获得出块奖励和交易手续费,需要注意的是以太坊PoS挖矿过程与PoW挖矿过程有很大的不同,在PoS机制下矿工需要拥有一定的以太坊数量来参与网络验证和出块,而不是通过计算复杂的数学问题来获得出块机会
文末小结
以太坊是一个基于区块链技术的智能合约平台,它不仅可以作为数字货币的交易平台还可以支持去中心化应用的开发和运行,以太坊的核心是以太币(ETH),它是支撑以太坊网络运行的主要货币,以太坊的发展和应用前景非常广阔,它已经成为区块链技术领域的重要代表之一,未来还有很大的发展空间
原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34123.html