如何把交易记录写到区块链上
一、 技术性地理解“在区块链上做标记”
区块链的本质是一个分布式的状态机。所谓的“做标记”或“记录交易”,实际上是改变全球共享状态的一个过程。这个过程由交易触发,并通过共识被永久地记录在一个区块中。
关键组件:
1. 交易(Transaction):
· 一个由外部账户(EOA)发起的、经过签名的数据包。它主要包含:
· from: 发送者地址
· to: 接收者地址(如果是合约创建,则为空)
· value: 要转移的以太币数量
· data: 调用智能合约函数的数据(Payload)
· gasLimit, gasPrice: 燃料相关参数
· nonce: 发送者的交易计数器(防止重放攻击)
2. 状态(State):
· 区块链的“当前快照”。在以太坊中,状态是一个庞大的默克尔帕特里夏树(Merkle Patricia Trie)。它映射了:
· 地址 -> 账户状态
· 账户状态分为两种:
· 外部账户(EOA): 有余额(balance)、nonce。
· 合约账户(CA): 有余额、nonce、存储根(storageRoot)、代码哈希(codeHash)。
· storageRoot 是该合约存储变量的默克尔树的根哈希。改变一个状态变量,就是在改变这棵存储树,从而改变整个全球状态的根哈希。
3. 区块(Block):
· 一个打包了多笔交易的数据结构。它包含:
· 区块头(父哈希、时间戳、状态根、交易根、收据根等)
· 交易列表
· 其他共识数据
· 状态根(stateRoot) 是区块头中最重要的字段之一,它是整个全球状态树根的哈希。任何微小的状态改变都会导致 stateRoot 彻底改变。
“标记”过程的技术流程:
1. 发起: 用户钱包构造一笔交易(如:调用合约的 transfer() 函数),并用私钥签名。
2. 广播: 签名的交易被广播到P2P网络中的一个节点。
3. 执行: 矿工/验证者节点将交易打包到一个候选区块中,并在本地EVM中模拟执行它。
· EVM(以太坊虚拟机) 是执行智能合约代码的运行时环境。它是一个隔离的、沙盒化的环境。
4. 状态转换: EVM的执行会读取当前状态,并根据合约逻辑计算出新的状态(例如:从A的余额中减去10,给B的余额加上10)。
5. 共识: 矿工完成工作量证明(PoW)或其他共识机制,将包含新 stateRoot 的区块广播出去。
6. 确认: 其他节点验证该区块和其中的所有交易。它们会重新执行所有交易,如果结果与新区块的 stateRoot 一致,则接受该区块,并将本地数据库中的状态更新为新区块所代表的状态。
所以,“做标记”在技术上是:通过执行交易,引发合约状态的改变,最终导致全球状态树根哈希的改变,并将这一改变通过共识永久固化在区块中。
---
二、 智能合约放在哪里?如何部署?
智能合约不是直接“存储”在某个服务器的文件夹里的。它的存在形式是:
1. 代码(Bytecode): 被部署到区块链上,成为区块链状态的一部分。
2. 地址(Address): 像一个银行账号一样,是合约在链上的唯一标识。
部署过程的技术细节:
1. 编写与编译: 开发者用Solidity等高级语言编写代码,然后用编译器(如 solc)将其编译成两种东西:
· 字节码(Bytecode): 这是EVM可以执行的机器代码。它将在部署时被永久地存储在链上。
· 应用二进制接口(ABI): 一个JSON文件,描述了合约的接口(有哪些函数、函数输入输出是什么类型)。ABI不存储在链上,而是由前端或调用者离线使用,以便正确地编码和解码与合约交互的数据。
2. 部署交易: 部署合约本身就是一笔特殊的交易。
· 这笔交易的 to 字段为空。
· 交易的 data 字段包含了编译好的合约字节码以及构造函数的参数。
3. 合约创建与地址生成: 当这笔特殊交易被矿工处理时,EVM会执行它:
· 运行字节码中的构造函数代码,初始化合约。
· 生成一个合约地址。这个地址通常由部署者地址和其nonce计算得出(keccak256(rlp.encode([sender, nonce]))[12:])。
· 将运行完成后的最终字节码和当前的合约存储状态存储在该新生成的地址下。
此后,这个合约地址就代表了这个智能合约。要调用它,只需要向这个地址发送交易,并在 data 字段中指定要调用的函数和参数。
---
三、 代码实例:一个简单的代币合约
让我们用一个经典的ERC20代币合约的简化版来演示。
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleToken {
// 状态变量:标记了地址到余额的映射
mapping(address => uint256) private _balances;
// 事件:用于在下文中“标记”和日志交易结果,可供前端监听
event Transfer(address indexed from, address indexed to, uint256 value);
// 构造函数:在部署时初始化总供应量,部署者获得所有代币
constructor(uint256 initialSupply) {
_balances[msg.sender] = initialSupply;
}
// 函数:用于查询余额
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
// 函数:核心转账逻辑,改变状态变量(做标记)
function transfer(address to, uint256 value) public returns (bool) {
require(_balances[msg.sender] >= value, "Insufficient balance");
// 这里是关键!改变状态变量,即在区块链上“做标记”
_balances[msg.sender] -= value;
_balances[to] += value;
// 触发事件,日志记录这次状态变更
emit Transfer(msg.sender, to, value);
return true;
}
}
```
交互流程:
1. 部署(Deploy):
· 你编译上述代码,得到字节码和ABI。
· 你发起一笔 to 为空、data 为(字节码 + initialSupply 参数编码)的交易。
· 交易成功后,链上生成一个新合约地址 0xContractAddress...。状态树中,_balances[你的地址] = initialSupply。
2. 调用转账(Call Transfer):
· 你想给朋友(地址 0xFriend...)转100个代币。
· 你的钱包会构造一笔交易:
· to: 0xContractAddress... (合约地址)
· value: 0 (不发送ETH)
· data: 函数选择器 + 参数编码。
· 函数选择器: transfer(address,uint256) 签名的Keccak哈希的前4字节,通常是 0xa9059cbb。
· 参数编码: 将 0xFriend... 和 100 编码成32字节的十六进制字符串,拼接在后面。
· 这笔交易被矿工执行,EVM运行 transfer 函数中的代码:
· _balances[你的地址] -= 100 (状态改变)
· _balances[0xFriend...] += 100 (状态改变)
· 这些状态变量的改变,最终体现在合约账户的存储树的变化上,并导致全球状态根(stateRoot) 的改变,被永久记录在区块中。
· 同时,一个 Transfer(你的地址, 0xFriend..., 100) 的事件日志(Log) 被发出,并被记录在交易的收据(Receipt) 中。前端应用可以监听这个事件来更新UI。
总结: 从技术角度看,在区块链上“做标记”就是通过执行交易来改变智能合约存储树中的状态变量。智能合约以字节码的形式存储在合约地址对应的账户中。部署合约是一笔特殊交易,而调用合约是在交易的data 字段中指定要执行的函数。所有的状态变更都通过共识机制被所有节点确认,并最终反映在不可篡改的全局状态树中。

浙公网安备 33010602011771号