区块和交易属性是Solidity中访问区块链信息的重要全局变量。本文详细介绍block、msg、tx等全局对象的属性,以及如何在实际开发中使用这些信息。

Block and Transaction


一、什么是区块和交易属性

一、1 基本概念

Solidity提供了多个全局对象来访问当前区块和交易的信息,包括block、msg、tx等。这些属性在智能合约中非常常用,用于获取时间戳、调用者地址、Gas价格等信息。

二、2 全局对象

1
block对象:
  • 包含当前区块信息
  • 时间戳、区块号、难度等
  • 只读属性
  • 所有节点相同
1
msg对象:
  • 包含当前消息/调用信息
  • 发送者、值、数据等
  • 函数调用相关
  • 可能在不同调用中不同
1
tx对象:
  • 包含交易信息
  • Gas价格、原始发送者
  • 整个交易相关
  • 交易级别信息

二、区块属性

三、1 基本属性

1
block.timestamp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pragma solidity ^0.8.0;

contract BlockInfo {
function getCurrentTime() public view returns (uint256) {
return block.timestamp; // 当前区块时间戳(秒)
}

function isAfter(uint256 targetTime) public view returns (bool) {
return block.timestamp >= targetTime;
}
`block.number:`solidity
function getCurrentBlock() public view returns (uint256) {
return block.number; // 当前区块号
}

function getBlocksSince(uint256 startBlock) public view returns (uint256) {
return block.number - startBlock;
}
`block.difficulty:`solidity
function getDifficulty() public view returns (uint256) {
return block.difficulty; // 当前区块难度
}
`block.gaslimit:`solidity
function getGasLimit() public view returns (uint256) {
return block.gaslimit; // 当前区块Gas限制
}
`block.coinbase:`solidity
function getMiner() public view returns (address) {
return block.coinbase; // 当前区块矿工地址
}

四、2 区块哈希

1
2
3
4
5
blockhash函数:
function getBlockHash(uint256 blockNumber) public view returns (bytes32) {
require(blockNumber < block.number && blockNumber >= block.number - 256,
"Block out of range");
return blockhash(blockNumber); // 只能查询最近256个区块

}

1
2
function getPreviousBlockHash() public view returns (bytes32) {
return blockhash(block.number - 1);

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

限制说明:
- 只能查询最近256个区块
- 超出范围返回0
- 用于验证历史区块
- 注意范围限制

## 三、消息属性

### 五、1 基本属性

msg.sender:
contract MessageInfo {
address public owner = msg.sender; // 部署者地址

function getCaller() public view returns (address) {
return msg.sender; // 函数调用者地址
}

function onlyOwner() public view {
require(msg.sender == owner, "Not owner");
// 只有owner可以调用
}
`msg.value:`solidity
function deposit() public payable {
balances[msg.sender] += msg.value; // 发送的以太币数量(wei)
}

function getValue() public view returns (uint256) {
return msg.value; // 当前调用发送的以太币
}
`msg.data:`solidity
function getCallData() public view returns (bytes memory) {
return msg.data; // 完整的调用数据
}

function getFunctionSelector() public view returns (bytes4) {
return msg.sig; // 函数选择器(前4字节)
}

六、2 函数选择器

1
2
3
4
msg.sig:
function transfer(address to, uint256 amount) public {
bytes4 selector = msg.sig; // transfer函数的选择器
// 可以用于路由调用

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

## 四、交易属性

### 七、1 基本属性

tx.gasprice:
function getGasPrice() public view returns (uint256) {
return tx.gasprice; // 交易的Gas价格
}

function calculateFee(uint256 gasUsed) public view returns (uint256) {
return gasUsed * tx.gasprice; // 计算交易费用
}
`tx.origin:`solidity
function getOrigin() public view returns (address) {
return tx.origin; // 交易的原始发起者
}

// 注意:tx.origin 和 msg.sender 的区别
// tx.origin: 交易的原始发起者(可能是合约调用链的起点)
// msg.sender: 直接调用者(可能是中间合约)

八、2 区别说明

1
2
3
4
msg.sender vs tx.origin:
contract A {
function callB(address b) public {
B(b).someFunction(); // msg.sender = A的地址
}
1
2
3
4
5
contract B {
function someFunction() public view returns (address, address) {
return (msg.sender, tx.origin);
// msg.sender: 直接调用者(A的地址)
// tx.origin: 原始发起者(用户地址)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

安全提示:
- 通常使用msg.sender
- tx.origin可能导致安全问题
- 避免使用tx.origin做权限检查
- 使用msg.sender更安全

## 五、Gas相关

### 九、1 gasleft函数

剩余Gas:
function checkGas() public view returns (uint256) {
return gasleft(); // 返回剩余Gas
}

function measureGas() public {
uint256 gasStart = gasleft();
// 执行一些操作
uint256 gasUsed = gasStart - gasleft();
// gasUsed 是执行操作的Gas消耗
}
`Gas优化:`solidity
function optimizedFunction() public {
uint256 gasBefore = gasleft();
// 执行操作
if (gasleft() < gasBefore - 100000) {
revert("Gas limit reached");
}

六、应用场景

十、1 时间锁

1
2
3
4
基于时间戳:
contract TimeLock {
mapping(address => uint256) public lockTime;
mapping(address => uint256) public lockedAmount;
1
2
3
4
function lock(uint256 duration) public payable {
require(msg.value > 0, "Must send ether");
lockTime[msg.sender] = block.timestamp + duration;
lockedAmount[msg.sender] = msg.value;
}
1
2
3
function withdraw() public {
require(block.timestamp >= lockTime[msg.sender], "Still locked");
require(lockedAmount[msg.sender] > 0, "No locked amount");
1
2
3
uint256 amount = lockedAmount[msg.sender];
lockedAmount[msg.sender] = 0;
lockTime[msg.sender] = 0;
1
payable(msg.sender).transfer(amount);
}
1
2
function isLocked(address account) public view returns (bool) {
return block.timestamp < lockTime[account];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

### 十一、2 随机数生成

基于区块信息:
contract RandomGenerator {
function generateRandom(uint256 max) public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(
block.timestamp,
block.difficulty,
msg.sender
))) % max;
}

// 注意:这不是真正的随机数,矿工可以操纵
// 仅用于非关键场景
}

十二、3 权限控制

1
2
3
4
基于调用者:
contract AccessControl {
address public owner = msg.sender;
mapping(address => bool) public isAdmin;
1
modifier onlyOwner() {
    _;
}
1
2
modifier onlyAdmin() {
require(isAdmin[msg.sender] || msg.sender == owner, "Not admin");
    _;
}
1
2
function grantAdmin(address user) public onlyOwner {
isAdmin[user] = true;
}
1
2
function revokeAdmin(address user) public onlyOwner {
delete isAdmin[user];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

### 十三、4 支付处理

处理以太币:
contract PaymentProcessor {
function processPayment(address recipient) public payable {
require(recipient != address(0), "Invalid recipient");

uint256 fee = msg.value / 100; // 1% 手续费
uint256 amount = msg.value - fee;

payable(recipient).transfer(amount);
payable(msg.sender).transfer(fee); // 退回手续费(示例)
}

function getPaymentInfo() public view returns (uint256, address) {
return (msg.value, msg.sender);
}

七、最佳实践

十四、1 时间戳使用

1
注意事项:
  • 矿工可以操纵时间戳(±15秒)
  • 不要用于关键随机数
  • 适合时间锁和过期检查
  • 考虑时间窗口
1
2
3
4
5
6
安全使用:
function safeTimeCheck(uint256 targetTime) public view returns (bool) {
// 使用时间窗口,允许一定误差
uint256 timeWindow = 300; // 5分钟
return block.timestamp >= targetTime - timeWindow &&
block.timestamp <= targetTime + timeWindow;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

### 十五、2 区块号使用

应用场景:
- 计算区块间隔
- 实现基于区块的时间锁
- 记录特定区块的状态
- 验证区块历史

示例:
mapping(uint256 => bool) public blockProcessed;

function processBlock() public {
require(!blockProcessed[block.number], "Already processed");
blockProcessed[block.number] = true;
// 处理逻辑
}

十六、3 安全考虑

1
2
3
4
避免tx.origin:
// 不推荐
function badCheck() public {
require(tx.origin == owner, "Not owner"); // 不安全

}

1
2
3
// 推荐
function goodCheck() public {
require(msg.sender == owner, "Not owner"); // 安全

}
Gas限制:solidity

1
2
function gasLimitedFunction() public {
require(gasleft() > 50000, "Insufficient gas");

}


## 八、常见问题

### 十七、1 时间戳精度

问题:
- 时间戳精度为秒
- 矿工可以操纵
- 不适合精确时间

解决:
- 使用时间窗口
- 考虑区块间隔
- 不用于关键随机数
- 适合相对时间

### 十八、2 区块哈希限制


- 检查范围
- 使用require验证
- 考虑替代方案
- 记录历史哈希

## 九、总结

区块和交易属性是Solidity中访问区块链信息的重要工具。关键要点:

全局对象:
- block:区块信息
- msg:消息/调用信息
- tx:交易信息
- gasleft:剩余Gas

- 时间锁
- 权限控制
- 支付处理
- 状态验证

最佳实践:
- 注意时间戳精度
- 避免使用tx.origin
- 检查区块哈希范围
- 合理使用Gas信息

通过深入理解这些全局属性,可以编写更灵活、更安全的智能合约,实现各种基于区块链信息的业务逻辑。

本文标题: Solidity区块和交易属性

发布时间: 2023年12月08日 00:00

最后更新: 2025年12月30日 08:54

原始链接: https://haoxiang.eu.org/4744933f/

版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!

× 喜欢就赞赏一下呗!
打赏二维码