区块链交易机制刨析
字数 2320 2025-08-22 12:22:59
区块链交易机制深度剖析
文章前言
区块链交易机制是区块链技术的核心组成部分,对区块链技术的应用和发展具有重要意义。本文将以以太坊的交易机制为例,深入剖析区块链交易机制的概念、特点及其实际应用。
基本介绍
交易本质
- 交易本质上是数据结构,包含交易参与者价值转移的相关信息
- 区块链是全球复式记账总账簿,每个交易都是区块链上的公开记录
- 区块链交易需要支付费用,用于交易执行的计算开销
- 计算开销使用Gas作为基本计价单位,通过GasPrice与其他货币换算
交易成员
交易参与者
-
交易发起者:
- 发起交易的参与者,通常是需要转移数字资产的一方
- 需要指定接收者、金额、手续费等信息并广播到网络
- 需要拥有足够的数字资产或代币完成交易
-
交易接收者:
- 接收交易的参与者,通常是需要获得数字资产的一方
- 在交易信息中确认身份和接收的数字资产数量
- 等待交易被打包进区块
- 可随时查询资产余额和交易历史记录
-
矿工:
- 负责验证交易和打包交易的参与者
- 通过解决密码学难题获得打包权利并获得奖励
- 验证交易的有效性和真实性,防止双重支付
- 遵守共识机制和网络协议维护网络稳定运行
-
节点:
- 维护网络运行和安全的参与者
- 分为全节点(存储完整区块链数据)和轻节点(存储部分数据)
- 验证交易有效性并广播到其他节点
- 遵守共识机制和网络协议维护网络安全
交易流程
完整交易流程
-
交易生成:
- 发起者向接收者发起交易,写入交易信息
- 信息包括:发起者、接收者、金额、手续费、时间戳等
- 发起者使用私钥对交易信息签名
-
交易广播:
- 交易信息通过P2P网络广播到其他节点
- 节点将交易信息传递给相邻节点,直到所有节点接收
-
交易验证:
- 节点验证交易有效性和真实性
- 验证发起者是否有足够资金
- 使用公钥解密交易信息
- 验证交易是否符合共识机制和网络协议
-
交易确认:
- 验证后交易被打包进区块并通过共识机制确认
- 区块加入区块链后交易不可篡改
- 确认速度取决于手续费和网络拥堵情况
交易流程示意图
[交易发起者] -- 生成交易信息 --> [区块链网络]
| |
| |
广播交易信息 广播交易信息
| |
v v
[节点] -- 验证交易信息 --> [矿工]
| |
| |
v v
[节点] <-- 交易确认消息 -- [矿工]
身份验证
验证机制
-
公钥加密:
- 使用接收者的公钥加密交易信息
- 只有拥有私钥的接收者能解密
- 保证交易信息不被篡改或窃取
-
数字签名:
- 使用发起者的私钥对交易信息签名
- 接收者使用公钥验证签名
- 确保交易信息不被篡改或伪造
验证流程
- 发起者使用私钥签名并广播交易
- 节点使用公钥解密和验证交易
- 交易有效则广播到相邻节点
- 矿工验证并打包交易
- 其他节点确认并更新本地数据库
身份验证机制示意图
[交易发起者] -- 使用私钥签名 --> [区块链网络]
| |
| |
广播签名后的交易信息 广播签名后的交易信息
| |
v v
[节点] -- 使用公钥解密验证 --> [矿工]
| |
| |
v v
[节点] -- 广播到相邻节点 --> [节点]
| |
| |
v v
[矿工] -- 验证打包区块 --> [区块链网络]
| |
| |
v v
[节点] <-- 确认消息和数据 -- [区块链网络]
交易费用
费用机制
- 交易费用是矿工打包交易获得的报酬,由发起者支付
- 发起者可自行设定交易费用金额
- 费用金额影响交易优先级和打包速度
费用计算
交易费用 = 交易数据大小(字节) × 矿工费用
示例计算:
- 交易数据大小:1000字节
- 矿工费用:0.0001 BTC/字节
- 交易费用 = 1000 × 0.0001 = 0.1 BTC
Go语言计算示例
package main
import (
"fmt"
)
func main() {
// 交易数据大小为1000字节
txSize := 1000
// 矿工费用为0.0001 BTC/字节
minerFee := 0.0001
// 计算交易费用
txFee := float64(txSize) * minerFee
fmt.Printf("交易费用为: %f BTC\n", txFee)
}
输出结果:
交易费用为: 0.100000 BTC
以太坊交易机制
流程视图
- 用户发起交易请求并用私钥签名
- 交易添加到交易池
- 矿工从交易池获取交易并打包
- 生成区块并进行共识出块
- 向全网广播交易和区块
数据流向
交易池数据来源:
- 本地提交:通过RPC服务提交
- 远程同步:通过广播同步其他节点的交易数据
交易池数据去向:
- 矿工获取并验证用于挖矿
- 成功挖矿后写入区块并广播
- 交易写入规范链后从池中删除
- 写入分叉的交易不会减少,等待重新打包
数据结构
TxPoolConfig配置
type TxPoolConfig struct {
Locals []common.Address // 本地账户地址
NoLocals bool // 是否开启本地交易机制
Journal string // 本地交易存放路径
Rejournal time.Duration // 持久化间隔
PriceLimit uint64 // 价格超出比例
PriceBump uint64 // 替换交易的最低价格涨幅百分比
AccountSlots uint64 // 单个账户可执行交易限制
GlobalSlots uint64 // 全部账户最大可执行交易
AccountQueue uint64 // 单个账户不可执行交易限制
GlobalQueue uint64 // 全部账户最大非执行交易
Lifetime time.Duration // queue中交易存活时间
}
默认配置
var DefaultTxPoolConfig = TxPoolConfig{
Journal: "transactions.rlp",
Rejournal: time.Hour,
PriceLimit: 1,
PriceBump: 10,
AccountSlots: 16,
GlobalSlots: 4096,
AccountQueue: 64,
GlobalQueue: 1024,
Lifetime: 3 * time.Hour,
}
TxPool数据结构
type TxPool struct {
config TxPoolConfig // 交易池配置
chainconfig *params.ChainConfig // 区块链配置
chain blockChain // blockchain接口
gasPrice *big.Int // gas价格
// ...其他字段...
pending map[common.Address]*txList // 可处理交易
queue map[common.Address]*txList // 不可处理交易
all *txLookup // 所有交易
priced *txPricedList // 按价格排序的交易
// ...其他字段...
}
基础配置
关键配置项
-
交易手续费上限:
var DefaultMaxPrice = big.NewInt(500 * params.GWei) -
交易池配置:
var DefaultTxPoolConfig = TxPoolConfig{ Journal: "transactions.rlp", Rejournal: time.Hour, PriceLimit: 1, PriceBump: 10, AccountSlots: 16, GlobalSlots: 4096, AccountQueue: 64, GlobalQueue: 1024, Lifetime: 3 * time.Hour, } -
交易检索数量:
TxLookupLimit: 2350000,
初始化交易池
初始化流程
- 调用
sanitize校验配置参数 - 使用默认配置初始化交易池
- 初始化本地账户并添加到交易池
- 创建按gas价格排序的交易列表
- 调用
reset更新交易池 - 启动reorg循环处理请求
- 加载本地交易(如启用)
- 订阅事件并启动主循环
关键代码
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
// 参数校验
config = (&config).sanitize()
// 创建交易池
pool := &TxPool{
config: config,
chainconfig: chainconfig,
chain: chain,
// ...初始化各字段...
}
// 初始化本地账户
pool.locals = newAccountSet(pool.signer)
for _, addr := range config.Locals {
pool.locals.add(addr)
}
// 创建按价格排序的交易列表
pool.priced = newTxPricedList(pool.all)
// 重置交易池
pool.reset(nil, chain.CurrentBlock().Header())
// 启动reorg循环
pool.wg.Add(1)
go pool.scheduleReorgLoop()
// 加载本地交易
if !config.NoLocals && config.Journal != "" {
pool.journal = newTxJournal(config.Journal)
pool.journal.load(pool.AddLocals)
}
// 订阅事件并启动主循环
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
pool.wg.Add(1)
go pool.loop()
return pool
}
构建交易
RPC请求示例
{
"jsonrpc": "2.0",
"method": "eth_sendTransaction",
"params": [{
"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0", // 30400
"gasPrice": "0x9184e72a000", // 10000000000000
"value": "0x9184e72a", // 2441406250
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}],
"id": 1
}
构建流程
- 检查账户是否存在
- 检查Nonce是否为空
- 调用
SignTx进行签名 - 调用
SubmitTransaction提交交易
关键代码
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
// 查找钱包账户
account := accounts.Account{Address: args.From}
wallet, err := s.b.AccountManager().Find(account)
// 处理Nonce
if args.Nonce == nil {
s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From)
}
// 设置默认值
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
// 组装交易并签名
tx := args.toTransaction()
signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
// 提交交易
return SubmitTransaction(ctx, s.b, signed)
}
交易入池
交易来源
- 本地提交:
AddLocals和AddLocal - 远程提交:
AddRemotes和AddRemotesSync
两者最终都调用addTxs实现交易添加。
添加流程
- 过滤已知交易
- 验证签名有效性
- 获取锁并调用
addTxsLocked - 请求提升可执行交易
关键代码
func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
// 过滤已知交易和无效签名交易
var (
errs = make([]error, len(txs))
news = make([]*types.Transaction, 0, len(txs))
)
for i, tx := range txs {
if pool.all.Get(tx.Hash()) != nil {
errs[i] = ErrAlreadyKnown
continue
}
if _, err := types.Sender(pool.signer, tx); err != nil {
errs[i] = ErrInvalidSender
continue
}
news = append(news, tx)
}
// 获取锁并添加交易
pool.mu.Lock()
newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
pool.mu.Unlock()
// 请求提升可执行交易
done := pool.requestPromoteExecutables(dirtyAddrs)
if sync {
<-done
}
return errs
}
交易签名
签名流程
- 检查钱包是否关闭
- 检查账户是否包含在钱包中
- 调用
SignTx进行签名 - 验证签名结果
关键代码
func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// 检查钱包状态
if w.device == nil {
return nil, accounts.ErrWalletClosed
}
// 检查账户
path, ok := w.paths[account.Address]
if !ok {
return nil, accounts.ErrUnknownAccount
}
// 签名交易
sender, signed, err := w.driver.SignTx(path, tx, chainID)
if err != nil {
return nil, err
}
// 验证签名
if sender != account.Address {
return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex())
}
return signed, nil
}
交易验证
验证场景
- 用户提交交易前验证
- 节点收到广播交易时验证
- 矿工打包交易时验证
- 节点同步区块时验证交易
验证内容
- 检查交易类型(EIP-2718)
- 检查交易大小
- 检查交易金额非负
- 检查Gas限制
- 验证签名有效性
- 检查GasPrice是否足够
- 检查Nonce顺序
- 检查账户余额是否足够
- 检查交易Gas是否足够
关键代码
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// 检查交易类型
if !pool.eip2718 && tx.Type() != types.LegacyTxType {
return ErrTxTypeNotSupported
}
// 检查交易大小
if uint64(tx.Size()) > txMaxSize {
return ErrOversizedData
}
// 检查金额非负
if tx.Value().Sign() < 0 {
return ErrNegativeValue
}
// 检查Gas限制
if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit
}
// 验证签名
from, err := types.Sender(pool.signer, tx)
if err != nil {
return ErrInvalidSender
}
// 检查GasPrice
if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 {
return ErrUnderpriced
}
// 检查Nonce顺序
if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow
}
// 检查账户余额
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
// 检查交易Gas
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
if err != nil {
return err
}
if tx.Gas() < intrGas {
return ErrIntrinsicGas
}
return nil
}
交易池重置
重置流程
- 检查是否需要重组(oldHead != newHead.ParentHash)
- 如果重组深度超过64则跳过
- 获取旧链和新链的区块
- 找出需要重新注入的交易
- 更新当前状态
- 重新注入被丢弃的交易
- 更新分叉指示器
关键代码
func (pool *TxPool) reset(oldHead, newHead *types.Header) {
// 处理重组
var reinject types.Transactions
if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
// 检查重组深度
oldNum := oldHead.Number.Uint64()
newNum := newHead.Number.Uint64()
if depth := uint64(math.Abs(float64(oldNum)-float64(newNum))); depth > 64 {
log.Debug("Skipping deep transaction reorg", "depth", depth)
} else {
// 获取旧链和新链区块
var discarded, included types.Transactions
rem := pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
add := pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
// 找出差异交易
for rem.NumberU64() > add.NumberU64() {
discarded = append(discarded, rem.Transactions()...)
rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1)
}
for add.NumberU64() > rem.NumberU64() {
included = append(included, add.Transactions()...)
add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1)
}
for rem.Hash() != add.Hash() {
discarded = append(discarded, rem.Transactions()...)
rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1)
included = append(included, add.Transactions()...)
add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1)
}
reinject = types.TxDifference(discarded, included)
}
}
// 更新当前状态
statedb, err := pool.chain.StateAt(newHead.Root)
if err != nil {
log.Error("Failed to reset txpool state", "err", err)
return
}
pool.currentState = statedb
pool.pendingNonces = newTxNoncer(statedb)
pool.currentMaxGas = newHead.GasLimit
// 重新注入交易
if len(reinject) > 0 {
senderCacher.recover(pool.signer, reinject)
pool.addTxsLocked(reinject, false)
}
// 更新分叉指示器
next := new(big.Int).Add(newHead.Number, big.NewInt(1))
pool.istanbul = pool.chainconfig.IsIstanbul(next)
pool.eip2718 = pool.chainconfig.IsBerlin(next)
}
文末小结
区块链交易机制是一种去中心化、透明且安全的交易方式,通过共识算法确保交易的有效性和网络的一致性。尽管交易确认时间可能较长,区块链技术的应用为各行业带来了新的可能性,但在实际应用中仍需解决可扩展性和能源效率等挑战。随着不断的创新和改进,区块链交易机制有望进一步改善并为未来的数字经济提供可靠的基础设施。