您正在查看: Ethereum-优秀转载 分类下的文章

openzeppelin ERC20合约的使用

推荐 :https://wizard.openzeppelin.com/

basic erc20

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
␊
contract MyToken is ERC20 {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 with snapshots

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, ERC20Snapshot, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function snapshot() public onlyOwner {␊
        _snapshot();␊
    }␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Snapshot)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 burnable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 burnable with snapshots

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable, ERC20Snapshot, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function snapshot() public onlyOwner {␊
        _snapshot();␊
    }␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Snapshot)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 pausable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, Pausable, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function pause() public onlyOwner {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyOwner {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 pausable with roles

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
import "@openzeppelin/contracts/access/AccessControl.sol";␊
␊
contract MyToken is ERC20, Pausable, AccessControl {␊
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
␊
    constructor() ERC20("MyToken", "MTK") {␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(PAUSER_ROLE, msg.sender);␊
    }␊
␊
    function pause() public onlyRole(PAUSER_ROLE) {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyRole(PAUSER_ROLE) {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 burnable pausable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable, Pausable, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function pause() public onlyOwner {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyOwner {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 burnable pausable with snapshots

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable, ERC20Snapshot, Ownable, Pausable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function snapshot() public onlyOwner {␊
        _snapshot();␊
    }␊
␊
    function pause() public onlyOwner {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyOwner {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override(ERC20, ERC20Snapshot)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 preminted

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
␊
contract MyToken is ERC20 {␊
    constructor() ERC20("MyToken", "MTK") {␊
        _mint(msg.sender, 1000 * 10 ** decimals());␊
    }␊
}␊
`

erc20 premint of 0

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
␊
contract MyToken is ERC20 {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 mintable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function mint(address to, uint256 amount) public onlyOwner {␊
        _mint(to, amount);␊
    }␊
}␊
`

erc20 mintable with roles

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/access/AccessControl.sol";␊
␊
contract MyToken is ERC20, AccessControl {␊
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊
␊
    constructor() ERC20("MyToken", "MTK") {␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(MINTER_ROLE, msg.sender);␊
    }␊
␊
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊
        _mint(to, amount);␊
    }␊
}␊
`

erc20 permit

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";␊
␊
contract MyToken is ERC20, ERC20Permit {␊
    constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊
}␊
`

erc20 votes

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊
␊
contract MyToken is ERC20, ERC20Permit, ERC20Votes {␊
    constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _afterTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Votes)␊
    {␊
        super._afterTokenTransfer(from, to, amount);␊
    }␊
␊
    function _mint(address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Votes)␊
    {␊
        super._mint(to, amount);␊
    }␊
␊
    function _burn(address account, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Votes)␊
    {␊
        super._burn(account, amount);␊
    }␊
}␊
`

erc20 flashmint

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol";␊
␊
contract MyToken is ERC20, ERC20FlashMint {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 full upgradeable transparent

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊
␊
contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, AccessControlUpgradeable, PausableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable {␊
    bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE");␊
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊
␊
    function initialize() initializer public {␊
        __ERC20_init("MyToken", "MTK");␊
        __ERC20Burnable_init();␊
        __ERC20Snapshot_init();␊
        __AccessControl_init();␊
        __Pausable_init();␊
        __ERC20Permit_init("MyToken");␊
        __ERC20FlashMint_init();␊
␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(SNAPSHOT_ROLE, msg.sender);␊
        _setupRole(PAUSER_ROLE, msg.sender);␊
        _mint(msg.sender, 2000 * 10 ** decimals());␊
        _setupRole(MINTER_ROLE, msg.sender);␊
    }␊
␊
    function snapshot() public onlyRole(SNAPSHOT_ROLE) {␊
        _snapshot();␊
    }␊
␊
    function pause() public onlyRole(PAUSER_ROLE) {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyRole(PAUSER_ROLE) {␊
        _unpause();␊
    }␊
␊
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊
        _mint(to, amount);␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override(ERC20Upgradeable, ERC20SnapshotUpgradeable)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _afterTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._afterTokenTransfer(from, to, amount);␊
    }␊
␊
    function _mint(address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._mint(to, amount);␊
    }␊
␊
    function _burn(address account, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._burn(account, amount);␊
    }␊
}␊
`

erc20 full upgradeable uups

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";␊
␊
contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, AccessControlUpgradeable, PausableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable, UUPSUpgradeable {␊
    bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE");␊
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");␊
␊
    function initialize() initializer public {␊
        __ERC20_init("MyToken", "MTK");␊
        __ERC20Burnable_init();␊
        __ERC20Snapshot_init();␊
        __AccessControl_init();␊
        __Pausable_init();␊
        __ERC20Permit_init("MyToken");␊
        __ERC20FlashMint_init();␊
        __UUPSUpgradeable_init();␊
␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(SNAPSHOT_ROLE, msg.sender);␊
        _setupRole(PAUSER_ROLE, msg.sender);␊
        _mint(msg.sender, 2000 * 10 ** decimals());␊
        _setupRole(MINTER_ROLE, msg.sender);␊
        _setupRole(UPGRADER_ROLE, msg.sender);␊
    }␊
␊
    function snapshot() public onlyRole(SNAPSHOT_ROLE) {␊
        _snapshot();␊
    }␊
␊
    function pause() public onlyRole(PAUSER_ROLE) {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyRole(PAUSER_ROLE) {␊
        _unpause();␊
    }␊
␊
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊
        _mint(to, amount);␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override(ERC20Upgradeable, ERC20SnapshotUpgradeable)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
␊
    function _authorizeUpgrade(address newImplementation)␊
        internal␊
        onlyRole(UPGRADER_ROLE)␊
        override␊
    {}␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _afterTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._afterTokenTransfer(from, to, amount);␊
    }␊
␊
    function _mint(address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._mint(to, amount);␊
    }␊
␊
    function _burn(address account, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._burn(account, amount);␊
    }␊
}␊
`

转载自:https://github.com/OpenZeppelin/contracts-wizard/blob/0dbf5d669adcdb8e801fbb67a269fc7931613d66/packages/core/src/erc20.test.ts.md

了解如何在 MetaMask 中设置交易 gas 费用

常见问题

EIP-1559 是什么?

EIP-1559 将改变以太坊的市场机制来支付交易费用。从根本上讲,EIP-1559 摆脱了首价拍卖,取而代之的是固定价格拍卖。这一变化的意义在于,提交交易的人不必猜测需要多少 gas,因为下一个区块中将包含明确的“基本费用”。对于想要优先处理交易的用户或应用程序,他们可以添加“小费”以支付矿工费用。您可以在此处更详细地了解 EIP-1559 以及它将如何改变以太坊。

EIP-1559 会使 ETH 通货紧缩吗?

EIP-1559
烧伤花掉的交易费基本费用的ETH。该 ETH 从供应中移除。在 EIP-1559 下,ETH 变得更加稀缺,因为以太坊上的所有交易都会消耗一定数量的 ETH。

对 EIP-1559 的通货紧缩程度进行准确建模是很困难的,因为您必须预测诸如预期交易之类的变量,甚至更难预测的是预期网络拥堵。从理论上讲,发生的交易越多,基本费用的燃烧对整个以太坊供应的通货紧缩压力就越大。根据网络上发生的交易数量,ETH 供应可能会在不同时间减少或膨胀更多

在与 Proof of Stake 合并后,Justin Drake 的模型估计每天将发行 1,000 ETH,并将燃烧 6,000 ETH 作为“最佳猜测”。假设有更多验证者加入,Staking APR 为 6.7%,那么年供应变化将为 -160 万 ETH,年供应率降低 1.4%。

尽管人们越来越意识到 MEV 和潜在的 EIP 会带来更高的透明度,但我们可以预期,随着机构金融交易者使用 DeFi 协议,套利机会只会变得更加复杂。这可能意味着每个区块的小费最终可能会比基本费用多得多。朱苏和Hasu实际预测,不到一半的今天的费用可以通过EIP
1559年

已经烧掉了多少 ETH?

你可以在这里跟踪。

作为 EIP-1559 的用户,我有什么变化?

这个想法是使用户的gas费用或多或少透明。因此,钱包将能够有更好的估计并使交易费用更可预测。他们不必过多依赖外部预言机,因为基本费用由协议本身管理。将有额外的用户体验优势,例如自动化费用竞标机制,从而减少交易确认的延迟。

此更改何时推出?

EIP-1559 将于 8 月 5 日在以太坊主网上上线,届时我们将开始推出此更改。目前,它在 Ropsten、Rinkeby、Goerli 网络中可用。

费用

EIP-1559 会使汽油更便宜吗?

不,这不是 EIP 的意图。作为更可预测的基本费用的副作用,如果我们假设费用的可预测性意味着用户不会频繁地为 Gas 多付钱,那么 EIP-1559 可能会导致 Gas 价格有所下降。使用 EIP-1559,在区块填充超过 50% 后,基础费用将增加和减少 12.5%。例如,如果一个区块 100% 满,基础费用增加 12.5%;如果是 50%,基本费用将相同;如果是 0%,基本费用将减少 12.5%。

应用程序向汇总和第 2 层的持续移动将大大降低费用。

如何设置正确的gas费用?

MetaMask 在设置 gas 费用时会为您提供三个选项:

  • 高:这是掉期或其他时间敏感交易的最佳选择。如果掉期处理时间过长,它通常会失败,您可能会损失资金。
  • 中:这有利于发送资产、提取资产或其他非时间敏感但重要的交易。
  • 低:仅应为处理时间不太重要的交易选择较低的 gas 费用。由于费用较低,很难预测您的交易何时(或是否)会成功。

我应该编辑gas费用吗?

您可以在高、中或低汽油费之间进行选择。请参阅上面的问题以了解这些含义。此外,您可以在“高级选项”设置中编辑您的 gas 限额、优先费用和最高费用。这将覆盖 MetaMask 为您“推荐”的低、中或高设置。

我的交易在 EIP-1559 上的速度有多快?

这暂时还不得而知。预测是由于 gas 费的可预测性,交易将进行得更快。

汽油费是如何计算的?

Gas 费用由区块的满量决定。EIP-1559 建立了交易包含的市场费率,并允许以太坊拥有双倍的区块空间(通过将每个区块的最大 gas 限制从 12.5M 增加到 25M)。然后它将块定位为只有 50% 满。

当网络利用率 > 50% 时,基础费用增加
当网络利用率 < 50% 时,基础费用减少

随着时间的推移,以太坊的区块大小将平均为相同的大小,但这种额外的容量允许具有交易包容性的灵活性。从本质上讲,它将消除设置天然气价格以进行交易的额外步骤。

MetaMask 的汽油费是否比其他任何地方都高?

不,MetaMask 使用与其他钱包如何估算 gas 价格类似的 gas 估算预言机的汇编。我们相信我们可以提供有竞争力的气体估算。

估计的gas费用与我实际支付的费用有多少不同?

估计的gas费用是一个范围。此范围的上限是用户将为交易支付的最高金额。在 EIP-1559 推出后,我们应该能够从我们的 gas 估计范围分析我们的交易包含率。

我应该考虑采用不同的方法来为掉期交易和其他交易设置 gas 费用吗?

MetaMask 将提供三个选项来设置 gas 费用:高、中和低(更多详细信息在“我如何设置正确的 gas 费用?”上方的常见问题解答中),并且会预先选择我们认为与您的交易类型相关的费用。如果您愿意,可以更改此设置,但我们会提供最佳建议。对于掉期等时间敏感的交易,我们建议并会预先选择“高”的gas费用。

什么是基本费用/最高费用/优先费用/等。是什么意思?

有关(新)术语的详细定义,请参阅我们的词汇表

由于我设置了“最高优先费用”,我如何才能看到我实际为交易支付的金额?

您可以查看已支付的估计 gas 费用的交易活动,或在区块浏览器(如 Etherscan)上查看交易收据中的“有效GasPrice”。

我可以在没有优先费(“矿工小费”)的情况下进行交易吗?

是的,你可以,但有可能其他交易(确实包括优先费)将被优先考虑,因为矿工被激励包含优先费的交易。

典型用户会选择“小费”金额还是为用户预选的总费用的一部分?

“小费”将作为用户在提交交易时看到的总体“汽油费”中包含在内。您还可以“编辑汽油费”以增加或减少称为“优先费”的“小费”。

在哪里可以看到他们为小费支付的费用?像 Etherscan 这样的区块浏览器现在会显示这些信息吗?

大多数工具将相应更新以显示与 EIP 相关的新信息。交易收据中现在将有一个“effectiveGasPrice”,它返回交易执行后支付的价格(即基本费用+优先费用)。

集成

EIP-1559 如何与 MetaMask 上的 Trezor/Ledger 集成一起工作?

Trezor 和 Ledger 尚不支持 EIP-1559,因此 MetaMask 将回退到 EIP-1559 之前的气体控制。

是在钱包还是 dapp 级别上实现?换句话说,我所有的 dapp 连接都会显示钱包中的新界面,还是只显示采用 EIP-1559 的 dapp?

如果 dapp 尚未切换到新的 EIP-1559 字段,MetaMask 将检测到这一点并使用 gasPrice 作为 maxFeePerGas。这意味着用户可能会为他们的交易多付钱。

用户体验

EIP-1559 还能如何改变钱包用户体验?

这将因钱包而异。这个想法是让基于区块需求的费用对用户更加透明。像 MetaMask 这样的钱包将能够有更好的估计,并且不必过多依赖外部预言机,因为基本费用由协议本身管理。此外,在使用 MetaMask 时,您可以决定高、中或低 gas 费。虽然会预先选择推荐类型,但用户可以在确认交易之前进行更改。此外,用户可以在“高级”设置中更改最高费用、最高优先费用和 Gwei。这样做将覆盖初始的高、中或低设置。

在网络拥塞期间,用户体验是否会有所不同?

在网络严重拥堵期间,基本费用将根据每个区块超过理想气体限制的需求量调整 12.5%,直到需求减弱。与首价拍卖不同,用户将更好地了解网络的拥堵程度,因为基本费用有多高。如果它太拥挤,用户可以支付或不支付该价格,就像他们在商店购买商品一样。或者,他们提交较低的费用并等待未来价格下跌。

开发者常见问题

作为 EIP-1559 的开发人员,我有什么变化?

我们建议 dapp 开发者和网络在伦敦分叉发生后立即分别切换到 EIP-1559 字段和区块头。如果不是,传统的 gasPrice 将用作 maxFeePerGas,这意味着用户可能会为他们的交易支付过高的费用。

如何使用 MetaMask 为我的 dapp 实施 EIP-1559?

您可以通读EIP-1559 规范,也可以查看Ethereum 的 JSON-RPC 规范中的 EIP-1559 部分。

是在用户级别还是 dapp 级别上实现?换句话说,如果我实施 EIP-1559,我的所有 dapp 用户会看到新的 gas 费用设置,还是只有同意采用 EIP-1559 的用户?

两者都是。用户通过客户端(钱包)与以太坊网络交互。用户还可以通过 dapp 与网络交互。如果 dapp 没有切换到新的 EIP-1559 字段,MetaMask 将检测到这一点并显示 1559 之前的气体估计 UI。

谁估计 gas 估计值,dapp 还是 MetaMask?

MetaMask.

所有的客户团队都准备好迎接 EIP-1559 了吗?

是的,客户规格已被冻结。伦敦硬分叉发布时间表如下

是否有关于复仇dapps任何潜在的风险?

答:所有的改变都是有风险的,但以太坊社区在强大的软件开发和协调方面有着良好的记录。EIP-1559 给对时间敏感的网络参与者(例如预言机)带来了一些潜在风险。Oracle 通常提供支持 DeFi 生态系统中各种参与者所需的定价信息。例如,Compound 需要知道用户抵押品的估值(即价格 x 资产数量),以确定他们的头寸是否需要清算。这些资产的估值必须不断更新,Compound 依靠预言机来更新这些信息。可以在这篇文章中找到有关这方面的更多信息。

对于伦敦硬分叉,我的项目可能需要考虑哪些更改?

EIP-1559(和 EIP-3198)的技术变化可能会影响您的项目,包括新的区块格式、新的交易格式、用于选择 gas 价格的新值、新的 JSON RPC 调用和几个 JSON RPC 的行为改变调用。有关这些更改的详细信息,请参阅此Infura 博客文章

词汇表

EIP-1559(以太坊改进协议 1559)

该提案最初由 Vitalik Buterin 创建,目的是通过不向矿工支付以太坊用户通过竞标天然气费而支付的天然气费来降低每笔交易的成本。以太坊用户现在可以根据网络的内部平均值对交易的平均汽油价格进行更准确的估计。如果我们假设费用的可预测性意味着用户不会频繁地为 gas 支付过高的费用,那么更可预测的基本费用的副作用可能会导致 gas 价格有所下降。有关 EIP-1559 将如何改变以太坊的更多信息,请参见此处

Gas fee

Gas 是指以太坊区块链上的交易费用。这是用户为验证或完成交易而支付的费用。

Base fee(基本费用)

由协议产生。表示将交易包含在区块中(即完成交易)所需的最小 gasUsed 乘数。这是交易费用中被烧掉的部分。

Max Priority fee - 即优先费或矿工的小费

MetaMask 最初将根据前一个区块的历史记录设置此金额。但是,用户将被允许在高级设置中编辑此金额。它被添加到交易中并代表支付给矿工的交易费用的一部分。

Max fee

MetaMask 最初将根据前一个区块的历史记录设置此金额。但是,用户将被允许在高级设置中编辑此金额。它代表用户愿意为其交易支付的最大金额(包括基本费用和最高优先级费用)。每gas最高费用与基本费用+最高优先费用之间的差额将“退还”给用户。

Gas limit

交易可能消耗的最大gas单位数量。

Gwei

Gwei 是以太的单位,最小的面额,代表 gigawei(或 1,000,000,000)。¹ Gwei 用于支付 gas 费用,或者说是用户支付的费用,以补偿处理和验证交易所需的计算能量在复仇blockchain.²

Slippage (滑点)

滑点是报价和执行价格之间的预期百分比差异。

原文:https://metamask.io/1559

争议不断的EIP-1559即将到来,它会造成哪些影响?

伦敦升级来临

以太坊基金会于7月15日公布了伦敦升级最新进展,本次升级将在主网达到12965000区块高度时被激活,具体时间预计在8月3日至5日之间。

V神在7月24号的世界区块链大会上演讲时说到:“当现在的PoW链完成了它的使命,PoW链上的一切都转移到PoS链上之后,就算是进入了以太坊2.0。但在两条链合并前期,将会执行一些较小的升级,引入分片,搭配Rollup,理想状态下可实现每秒10万笔交易。”而即将在PoW链上进行的伦敦分叉就是合并前期的一次重要升级。

从2015年开始,以太坊经历了数次升级,它们帮助以太坊不断完善并走向2.0时代。

什么是EIP-1559

以太坊的网络升级通过链下治理实施,社区成员提出以太坊改进提案(Ethereum Improvement Proposal,EIP)并对升级所要采用的EIP内容达成一致,由开发团队更新客户端,最后矿工使用最新版的以太坊客户端并通过硬分叉升级。

伦敦硬分叉升级中包含了五个以太坊改进提案,包括EIP-1559、EIP-3198、EIP-3529、EIP-3541和EIP-3554。伦敦升级因EIP-1559备受瞩目,其将改进现有的手续费机制,并将持续销毁ETH。

目前以太坊的交易手续费(Gas费)计算机制中一部分由创建者设置,创建者设置的交易费用多少会影响到矿工打包的先后顺序。矿工提供了在以太坊上运行交易和智能合约的计算能力,他们通常会优先处理设定高Gas费的用户的交易,以最大限度地获得区块奖励。

同样,为了能够更快完成交易,交易创建者也会设置更高的手续费来激励矿工。因此,在这种机制的刺激下,导致竞争巨大、Gas费不断上升、效率极低,并导致验证节点总价超付,增加了成本。

为了改善用户体验,V神和以太坊核心开发人员提出EIP-1559改进提案,在EIP-1559提案中,交易手续费将被拆分为两部分:基础费+矿工小费。其中基础费是交易所需的最少花费,由系统直接销毁,小费归矿工所有。

基础费用是用户在以太坊上发送交易或完成操作所需的最低Gas价格。它根据以太坊上每个区块使用的空间大小而波动。理想情况下,以太坊上的每个区块最多可容纳1500万Gas。然而在网络拥堵时,EIP-1559将允许块大小增加到这个数量的两倍。如果区块包含的Gas超过1500万,基础费将提高12.5%,反之如果低于1500万,将降低12.5%,直至趋于零。这种调整旨在确保以太坊的Gas费趋向于平均每区块约1500万Gas。

矿工小费指的是用户可以选择在最低基本费用的基础上再支付额外费用,该费用将直接支付给矿工,以激励矿工优先处理某些交易。EIP-1559中的小费是可选的,仅用于需要最快网络确认时间的用户。

EIP-1559对我们会产生哪些影响?

那么,EIP-1559设定的相对固定的Gas基础费的作用有哪些呢?

其一,可以降低ETH的通胀率。目前,ETH的供应量是无限的,每产生一个新的区块,就会有两枚新的ETH诞生,因此,它是有通胀的属性在的。但EIP-1559对于Gas基础费的销毁机制可能会带来一种局面,在交易活跃度较高时,用户支付基础费用销毁的ETH总量可能会抵消并大于通过区块奖励新发行的ETH总额,这将意味着ETH供应量出现负增长。如果交易不活跃,销毁的基础费少于出块奖励,那也可以促进ETH数量不会过分减少,使其达到一种平衡状态。

其二,降低以太坊交易费用波动性。根据EIP-1559,Gas基础费只能增减12.5%,这为以太坊的交易费用带来了稳定性。但是,降低波动性不代表降低交易费用,高费用问题主要是由于处理交易的网络容量有限造成的。以太坊费用机制的变化不能改变网络一次能够处理多少交易。这属于以太坊的可拓展性问题,需要交给Layer 2扩容方案和未来的分片技术。

基于EIP-1559的手续费机制和作用,我们可以看到它将主要影响三种不同类型的利益相关者:ETH投资者、矿工和用户。

(一)ETH投资者

基础费的销毁降低了ETH的通胀率。如果以太坊交易非常活跃时,甚至可能出现其销毁量高于新增量的情况,在这种情况下,ETH会变成通缩资产。

现在每天ETH的产出是13000枚左右,年通胀率约为4%。Dune Analytics的数据显示,如果按照EIP-1559的方式,一年内预计销毁2937123枚以太坊,占增发量的61.9%,可以把通胀率降低到1.5%,这个数值要比比特币1.8%的通胀率更低。通过销毁基础费,在一定程度上会提升ETH的价值,有利于长期持有ETH的投资者们。

(二)矿工

表面来看,EIP-1559中基础费的销毁会极大地削减矿工收入。但这是绝对的吗?

我们首先来看一下矿工的收入来源是什么。目前,矿工收入来源于交易手续费和区块奖励。当EIP-1559落实之后,交易手续费拆分为基础费和矿工小费,其中基础费被销毁。但随着以太坊上DEX的崛起,还有一种矿工收入出现,即矿工可提取价值(MEV)。MEV是一种可变的收入来源,DEX交易者可能比普通用户更看重链上交易执行的速度和顺序,矿工可利用这一点从DEX交易者那里获得更多的回报。

由于DEX越来越受欢迎,MEV变得越来越有利可图。研究和开发组织Flashbots估计,MEV的日收入已从2021年初的50万美元增长到2021年6月的600多万美元。

因此,在EIP-1559之后,矿工的MEV部分的收益比例有可能会越来越多。EIP-1559对矿工的收益肯定会有影响,但可能不一定有想象中的大。矿工依然有机会获得不错的收益。此外,EIP-1559本身会对ETH带来增值效应,这对所有人都是有利的。

(三)用户

对于以太坊的用户来说,高昂的费用是目前最紧迫的问题之一。EIP-1559并不会解决这个问题,并没有改变以太坊本身一次只能处理有限数量交易的事实。EIP-1559的主要作用是可以优化和提高以太坊手续费的使用效率。

EIP-1559提高了以太坊费用方式的透明度和可见性。过去用户在支付手续费时,需要根据网络拥堵情况和近期Gas费估算手续费多少。如果过少等待时间会变长,过多则造成浪费。从区块层面看,EIP-1559的方案每次区块之间的基础费变化幅度最多为12.5%,根据规则用户能预测并支付相对准确的手续费,以提高用户体验。

EIP-1559面临的风险

EIP-1559关于手续费的改变可以为以太坊带来好的一面,但技术是双刃剑,有好处但也同样伴随着风险。

对于矿工来说,随着EIP-1559的激活,他们的收入将减少。矿工将失去大额的交易手续费,而只能从寻求交易优先权的用户那里获得小费。改变奖励机制本身不会影响以太坊处理区块或计算的能力。然而,却存在着不满的矿工离开网络、破坏网络或启动竞争链的可能性。如果有很大一部分以太坊矿工退出或造反,区块时间和网络安全将受到负面影响。

对于用户和DAPP开发者,EIP-1559的激活可能不会表现得像理论上那样有效。如果不能实现承诺的费用市场效率,可能会导致用户和开发者的幻想破灭。如果发生这种情况,以太坊的竞争对手,如Binance Smart Chain和Cardano(按市值计算仅次于以太坊的两个最大的智能合约区块链平台),无疑将抓住机会抢占市场份额。

最后,EIP-1559的激活还带来了不可预见的错误或恶意的用户行为的风险。这在私人测试网络上测试EIP-1559的过程中,已经发现了一些。

以太坊的新时代

EIP-1559通过让手续费更有效率来提升用户体验,但不能从根本上解决网络拥堵和高手续费的问题,这需要通过Layer 2扩容或者以太坊2.0来实现。

EIP-1559将极大地增强以太坊上处理交易的用户体验。当然,矿工们会担心EIP-1559的费用销毁方面,但EIP-1559的总体好处远远超过费用销毁,将对终端用户产生积极的影响。

随着EIP-1559、Layer 2以及PoS的推进,以太坊及ETH会有向好的变化,其中也会潜藏一些危机。不过,如果一切都能顺利落地,这会开启以太坊的新时代,对整个加密领域的格局产生非常重大的影响。

转载自:https://zhuanlan.zhihu.com/p/395153612

其他

https://notes.ethereum.org/@vbuterin/eip-1559-faq
https://zhuanlan.zhihu.com/p/393196358
https://zhuanlan.zhihu.com/p/396948190
https://zhuanlan.zhihu.com/p/361104358

以太坊交易池架构设计

当前以太坊公链的平均每秒能处理30到40笔交易,因此以太坊一旦出现火热的DAPP时,极易出现交易拥堵。

偏底的交易处理速度永远无法同现有的中心化服务相比。当网络中出现大量交易排队时,矿工是如何选择并管理这些交易的呢?答案在本篇所介绍的以太坊交易池中,如果你对交易还不特别熟悉,则请先阅读 [以太坊交易]({{< ref "part1/transaction.md" >}})。

交易处理流程

当你通过以太坊钱包,发送一笔转账交易给张三时。这笔交易是如何进入网络,最终被矿工打包到区块中呢?

下图是一笔交易从出生到交易进入区块的关键流程。

transaction-life

首先,用户可通过以太坊钱包或者其他调用以太坊节点API (eth_sendRawTransaction等)发送交易到一个运行中的以太坊 geth 节点。

此时,因为交易时通过节点的API接收,因此此交易被视为一笔来自本地(local)(图中用红球表示),在经过一系列校验和处理后。交易成功进入交易池,随后向已连接的邻近节点发送此交易。

当邻近节点,如矿工节点从邻近节点接收到此交易时,在进入交易池之前。会将交易标记为来自远方(remote)的交易(图中用绿球表示)。也需要经过校验和处理后,进入矿工节点的交易池,等待矿工打包到区块中。

如果邻近节点,不是矿工,也无妨。因为任何节点会默认将接受到得合法交易及时发送给邻近节点。得益于P2P网络,一笔交易平均在6s内扩散到整个以太坊公链网络的各个节点中。

A-Distributed-P2P-Network-with-Elements-of-Blockchain-and-Cryptocurrency

进入以太坊交易池的交易被区分本地还是远方的目的是因为,节点对待local的交易和remote的交易有所差异。简单地说是 local 交易优先级高于 remote 交易。

以太坊交易池设计

前面并未交易池处理细节,这里将详细讲解以太坊交易池处理一笔交易时的完整过程。在讲解前,你还还有先了解以太坊交易池的设计模型。 从2014年到现在,以太坊的交易池一直在不断优化中,从未停止。从这里也说明,交易池不仅仅重要,还需要高性能。

下图是以太坊交易池的主要设计模块,分别是交易池配置、实时的区块链状态、交易管理容器、本地交易存储和新交易事件。

ethereum-tx-pool-desgin

各个模块相互影响,其中最重要的的交易管理。这也是需要我们重点介绍的部分。

交易池配置

交易池配置不多,但每项配置均直接影响交易池对交易的处理行为。配置信息由 TxPoolConfig 所定义,各项信息如下:

// core/tx_pool.go:125
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
}
  • Locals: 定义了一组视为local交易的账户地址。任何来自此清单的交易均被视为 local 交易。
  • NoLocals: 是否禁止local交易处理。默认为 fasle,允许 local 交易。如果禁止,则来自 local 的交易均视为 remote 交易处理。
  • Journal: 存储local交易记录的文件名,默认是 ./transactions.rlp
  • Rejournal:定期将local交易存储文件中的时间间隔。默认为每小时一次。
  • PriceLimit: remote交易进入交易池的最低 Price 要求。此设置对 local 交易无效。默认值1。
  • PriceBump:替换交易时所要求的价格上调涨幅比例最低要求。任何低于要求的替换交易均被拒绝。
  • AccountSlots: 当交易池中可执行交易(是已在等待矿工打包的交易)量超标时,允许每个账户可以保留在交易池最低交易数。默认值是 16 笔。
  • GlobalSlots: 交易池中所允许的可执行交易量上限,高于上限时将释放部分交易。默认是 4096 笔交易。
  • AccountQueue:交易池中单个账户非可执行交易上限,默认是64笔。
  • GlobalQueue: 交易池中所有非可执行交易上限,默认1024 笔。
  • Lifetime: 允许 remote 的非可执行交易可在交易池存活的最长时间。交易池每分钟检查一次,一旦发现有超期的remote 账户,则移除该账户下的所有非可执行交易。默认为3小时。

上面配置中,包含两个重要概念可执行交易非可执行交易。可执行交易是指从交易池中择优选出的一部分交易可以被执行,打包到区块中。非可执行交易则相反,任何刚进入交易池的交易均属于非可执行状态,在某一个时刻才会提升为可执行状态。

一个节点如何自定义上述交易配置呢?以太坊 geth 节点允许在启动节点时,通过参数修改配置。可修改的交易池配置参数如下(通过 geth -h 查看):

TRANSACTION POOL OPTIONS:
  --txpool.locals value        Comma separated accounts to treat as locals (no flush, priority inclusion)
  --txpool.nolocals            Disables price exemptions for locally submitted transactions
  --txpool.journal value       Disk journal for local transaction to survive node restarts (default: "transactions.rlp")
  --txpool.rejournal value     Time interval to regenerate the local transaction journal (default: 1h0m0s)
  --txpool.pricelimit value    Minimum gas price limit to enforce for acceptance into the pool (default: 1)
  --txpool.pricebump value     Price bump percentage to replace an already existing transaction (default: 10)
  --txpool.accountslots value  Minimum number of executable transaction slots guaranteed per account (default: 16)
  --txpool.globalslots value   Maximum number of executable transaction slots for all accounts (default: 4096)
  --txpool.accountqueue value  Maximum number of non-executable transaction slots permitted per account (default: 64)
  --txpool.globalqueue value   Maximum number of non-executable transaction slots for all accounts (default: 1024)
  --txpool.lifetime value      Maximum amount of time non-executable transaction are queued (default: 3h0m0s)

链状态

所有进入交易池的交易均需要被校验,最基本的是校验账户余额是否足够支付交易执行。或者 交易 nonce 是否合法。在交易池中维护的最新的区块StateDB。当交易池接收到新区块信号时,将立即重置 statedb。

在交易池启动后,将订阅链的区块头事件:

//core/tx_pool.go:274
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)

并开始监听新事件:

//core/tx_pool.go:305
for {
   select {
   // Handle ChainHeadEvent
   case ev := <-pool.chainHeadCh:
      if ev.Block != nil {
         pool.mu.Lock()
         if pool.chainconfig.IsHomestead(ev.Block.Number()) {
            pool.homestead = true
         }
         pool.reset(head.Header(), ev.Block.Header())
         head = ev.Block

         pool.mu.Unlock()
      }
  //...
  }
}

接收到事件后,将执行 func (pool *TxPool) reset(oldHead, newHead *types.Header)方法更新 state和处理交易。核心是将交易池中已经不符合要求的交易删除并更新整理交易,这里不展开描述,有兴趣的话,可以到微信群中交流。

本地交易

在交易池中将交易标记为 local 的有多种用途:

  1. 在本地磁盘存储已发送的交易。这样,本地交易不会丢失,重启节点时可以重新加载到交易池,实时广播出去。
  2. 可以作为外部程序和以太坊沟通的一个渠道。外部程序只需要监听文件内容变化,则可以获得交易清单。
  3. local交易可优先于 remote 交易。对交易量的限制等操作,不影响 local 下的账户和交易。

对应本地交易存储,在启动交易池时根据配置开启本地交易存储能力:

//core/tx_pool.go:264
if !config.NoLocals && config.Journal != "" {
        pool.journal = newTxJournal(config.Journal)
        if err := pool.journal.load(pool.AddLocals); err != nil {
            log.Warn("Failed to load transaction journal", "err", err)
        }
    //...
}

并从磁盘中加载已有交易到交易池。在新的local 交易进入交易池时,将被实时写入 journal 文件。

// core/tx_pool.go:757
func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
   if pool.journal == nil || !pool.locals.contains(from) {
      return
   }
   if err := pool.journal.insert(tx); err != nil {
      log.Warn("Failed to journal local transaction", "err", err)
   }
}

从上可看到,只有属于 local 账户的交易才会被记录。你又没有注意到,如果仅仅是这样的话,journal 文件是否会跟随本地交易而无限增长?答案是否定的,虽然无法实时从journal中移除交易。但是支持定期更新journal文件。

journal 并不是保存所有的本地交易以及历史,他仅仅是存储当前交易池中存在的本地交易。因此交易池会定期对 journal 文件执行 rotate,将交易池中的本地交易写入journal文件,并丢弃旧数据。

journal := time.NewTicker(pool.config.Rejournal)
//...
//core/tx_pool.go:353
case <-journal.C:
            if pool.journal != nil {
                pool.mu.Lock()
                if err := pool.journal.rotate(pool.local()); err != nil {
                    log.Warn("Failed to rotate local tx journal", "err", err)
                }
                pool.mu.Unlock()
            }
}

新交易信号

文章开头,有提到进入交易池的交易将被广播到网络中。这是依赖于交易池支持外部订阅新交易事件信号。任何订阅此事件的子模块,在交易池出现新的可执行交易时,均可实时接受到此事件通知,并获得新交易信息。

需要注意的是并非所有进入交易池的交易均被通知外部,而是只有交易从非可执行状态变成可执行状态后才会发送信号。

//core/tx_pool.go:705
go pool.txFeed.Send(NewTxsEvent{types.Transactions{tx}})
//core/tx_pool.go:1022
go pool.txFeed.Send(NewTxsEvent{promoted})

在交易池中,有两处地方才会执行发送信号。一是交易时用于替换已经存在的可执行交易时。二是有新的一批交易从非可执行状态提升到可执行状态后。

外部只需要订阅SubscribeNewTxsEvent(ch chan<- NewTxsEvent)新可执行交易事件,则可实时接受交易。在 geth 中网络层将订阅交易事件,以便实时广播。

//eth/handler.go:213
pm.txsCh = make(chan core.NewTxsEvent, txChanSize)
pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh)
//eth/handler.go:781
func (pm *ProtocolManager) txBroadcastLoop() {
   for {
      select {
      case event := <-pm.txsCh:
         pm.BroadcastTxs(event.Txs)
      //...
   }
}

另外是矿工实时订阅交易,以便将交易打包到区块中。

//miner/worker.go:207
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
//miner/worker.go:462
txs := make(map[common.Address]types.Transactions)
for _, tx := range ev.Txs {
        acc, _ := types.Sender(w.current.signer, tx)
    txs[acc] = append(txs[acc], tx)
}
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)
w.commitTransactions(txset, coinbase, nil)

交易管理

最核心的部分则是交易池对交易的管理机制。以太坊将交易按状态分为两部分:可执行交易和非可执行交易。分别记录在pending容器中和 queue 容器中。

ethereum-tx-pool-txManager

如上图所示,交易池先采用一个 txLookup (内部为map)跟踪所有交易。同时将交易根据本地优先,价格优先原则将交易划分为两部分 queue 和 pending。而这两部交易则按账户分别跟踪。

那么在交易在进入交易池进行管理的细节有是如何的呢?等我下一篇文章详细介绍以太坊交易池交易管理。

转载自:https://learnblockchain.cn/books/geth/part2/txpool/txpool.html

以太坊本地待处理交易存储

上篇在介绍交易池时有讲到对于本地交易的特殊处理。为了不丢失未完成的本地交易,以太坊交易池通过 journal 文件存储和管理当前交易池中的本地交易,并定期更新存储。

下图是交易池对本地待处理交易的磁盘存储管理流程,涉及加载、实时写入和定期更新维护。
以太坊交易池本地待处理交易存储管理

加载已存储交易

在交易池首次启动 journal 时,将主动将该文件已存储的交易加载到交易池。

//core/tx_journal.go:61
if _, err := os.Stat(journal.path); os.IsNotExist(err) { //❶
   return nil
}
// Open the journal for loading any past transactions
input, err := os.Open(journal.path) //❷
if err != nil {
   return err
}
defer input.Close()

处理时,如果文件不存在则退出 ❶,否则 Open 文件,获得 input 文件流 ❷。

//core/tx_journal.go:76
stream := rlp.NewStream(input, 0)//❸
total, dropped := 0, 0

因为存储的内容格式是 rlp 编码内容,因此可以直接初始化 rlp 内容流 ❸,为连续解码做准备。

var (
   failure error
   batch   types.Transactions
)
for {
   tx := new(types.Transaction)
   if err = stream.Decode(tx); err != nil { //❹
      if err != io.EOF {
         failure = err
      }
      if batch.Len() > 0 {//❼
         loadBatch(batch)
      }
      break
   }
   total++

   if batch = append(batch, tx); batch.Len() > 1024 {//❺
      loadBatch(batch)//❻
      batch = batch[:0]
   }
}

直接进入 for 循环遍历,不断从 stream 中一笔笔地解码出交易❹。但交易并非单笔直接载入交易池,而是采用批量提交模式,每 1024 笔交易提交一次 ❺。 批量写入,有利于降低交易池在每次写入交易后的更新。一个批次只需要更新(排序与超限处理等)一次。当然在遍历结束时(err==io.EOF),也需要将当前批次中的交易载入❼。

loadBatch := func(txs types.Transactions) {
   for _, err := range add(txs) {
      if err != nil {
         log.Debug("Failed to add journaled transaction", "err", err)
         dropped++ //❽
      }
   }
}

loadBatch 就是将交易一批次加入到交易池,并获得交易池的每笔交易的处理情况。如果交易加入失败,则进行计数 ❽。最终在 load 方法执行完毕时,显示交易载入情况。

log.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped)

存储交易

以太坊存储本地交易

当交易池新交易来自于本地账户时❶,如果已开启记录本地交易,则将此交易加入journal ❷。到交易池时,将实时存储到 journal 文件中。

//core/tx_pool.go:757
func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
   // Only journal if it's enabled and the transaction is local
   if pool.journal == nil || !pool.locals.contains(from) {//❶
      return
   }
   if err := pool.journal.insert(tx); err != nil { //❷
      log.Warn("Failed to journal local transaction", "err", err)
   }
}

journal.insert则将交易实时写入文件流中❸,相当于实时存储到磁盘。而在写入时,是将交易进行RLP编码。

//core/tx_journal.go:120
func (journal *txJournal) insert(tx *types.Transaction) error {
   if journal.writer == nil {
      return errNoActiveJournal
   }
   if err := rlp.Encode(journal.writer, tx); err != nil {//❸
      return err
   }
   return nil
}

这里引发了在上面载入已存储交易时将交易重复写入文件。因此在加载交易时,使用一个 空 writer 替代 ❹。

//core/tx_journal.go:72
journal.writer = new(devNull) //❹
defer func() { journal.writer = nil }() //❺

并且在加载结束时清理❺。

定期更新 journal

image-20190622234757114

journal 的目的是长期存储本地尚未完成的交易,以便交易不丢失。而文件内容属于交易的RLP编码内容,不便于实时清空已完成或已无效的交易。因此以太坊采取的是定期将交易池在途交易更新到 journal 文件中。

首先,在首次加载文件中的交易到交易池后,利用交易池的检查功能,将已完成或者已完成的交易拒绝在交易池外。在加载完成后,交易池中的交易仅仅是本地账户待处理的交易,因此在加载完成后❶,立即将交易池中的所有本地交易覆盖journal文件❷。

//core/tx_pool.go:264
pool.journal = newTxJournal(config.Journal)

if err := pool.journal.load(pool.AddLocals); err != nil {//❶
   log.Warn("Failed to load transaction journal", "err", err)
}
if err := pool.journal.rotate(pool.local()); err != nil {//❷
   log.Warn("Failed to rotate transaction journal", "err", err)
}

在 rotate 中,并非直接覆盖。而是先创建另一个新文件❸,将所有交易RLP编码写入此文件❹ 。

replacement, err := os.OpenFile(journal.path+".new",  //❸
                                os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
   return err
}
journaled := 0
for _, txs := range all {
   for _, tx := range txs {
      if err = rlp.Encode(replacement, tx); err != nil {//❹
         replacement.Close()
         return err
      }
}
   journaled += len(txs)
}
replacement.Close()

写入完毕,将此文件直接移动(重命名),已覆盖原 journal 文件。

if err = os.Rename(journal.path+".new", journal.path); err != nil {
   return err
}

其次,是交易池根据参数 txpool.rejournal 所设置的更新间隔定期更新❺。将交易池中的本地交易存储到磁盘❻。

//core/tx_pool.go:298
journal := time.NewTicker(pool.config.Rejournal)//❺
//...
for {
  select {
    //...
    case <-journal.C:
            if pool.journal != nil {
                pool.mu.Lock()
                if err := pool.journal.rotate(pool.local()); err != nil { //❻
                    log.Warn("Failed to rotate local tx journal", "err", err)
                }
                pool.mu.Unlock()
            }
        }
  }
}

上述,是以太坊交易池对于本地交易进行持久化存储管理细节。
转载自:https://learnblockchain.cn/books/geth/part2/txpool/txjournal.html