您正在查看: 2022年2月

Solidity — 启用 ABIEncoderV2 以使用 Structs 作为函数参数

如果您一直在以太坊上进行开发,您就会知道无法将结构从合约传递到合约或从 web3 传递到合约的痛苦。在 Atra Blockchain Services,我们为用户自动创建和部署以太坊合约,这一限制直接影响了我们的数据存储服务 dTables。

现在,启用 ABIEncoderV2 后,您可以将结构类型从 web3 或其他合约传递给函数。在启用 ABIEncoderV2 的情况下编译合约时,编译后的 ABI 输出会发生一些变化。ABI JSON 现在将包含一种称为“元组”的新类型,当它遇到结构作为函数中的参数时。元组类型与属性“组件”配对,组件属性是一个包含 {name, type} 对象列表的数组。
下面是使用结构体作为输入参数的合约的 ABI 示例。注意类型和组件属性。
GitHub code

{
  "constant": false,
  "inputs": [{
    "components": [{
      "name": "text",
      "type": "string"
    }],
    "name": "recordData",
    "type": "tuple"
  }],
  "name": "Insert",
  "outputs": [{
    "name": "success",
    "type": "bool"
  }],
  "payable": false,
  "stateMutability": "nonpayable",
  "type": "function"
}

下面是一个使用新编码器的示例存储合约 (GitHub code

pragma solidity ^0.5.3;
//注意此处
pragma experimental ABIEncoderV2;  //0.7.0 之前需添加支持
pragma abicoder v2; // 0.7.0 之后需要添加

contract storageContract {

  event Inserted(address _sender, address _recordId);
  event Updated(address _sender, address _recordId);
  event Deleted(address _sender, address _recordId);

  struct Data {
    string text;
  }
  struct Record {
    Data data;
    uint idListPointer;
  }

  mapping(address => Record) public Table;
  address[] public IdList;

  constructor() public {}

  // Check if recordId is in IdList, it's common for the record to be deleted and not by in the IdList anymore
  function Exists(address recordId) public view returns(bool exists) {
    if (IdList.length == 0) return false;
    return (IdList[Table[recordId].idListPointer] == recordId);
  }

  function GetLength() public view returns(uint count) {
    return IdList.length;
  }

  function GetByIndex(uint recordIndex) public view returns(address recordId, Data memory record) {
    require(recordIndex < IdList.length);
    return (IdList[recordIndex], Table[IdList[recordIndex]].data);
  }

  function GetById(address recordId) public view returns(uint index, Data memory record) {
    require(Exists(recordId));
    return (Table[recordId].idListPointer, Table[recordId].data);
  }

  function Insert(Data memory recordData) public returns(bool success) {
    address recordAddress = address(now);
    require(!Exists(recordAddress));
    Table[recordAddress].data = recordData;
    Table[recordAddress].idListPointer = IdList.push(recordAddress) - 1;
    emit Inserted(msg.sender, recordAddress);
    return true;
  }

  function Update(address recordId, Data memory recordData) public returns(bool success) {
    require(Exists(recordId));
    Table[recordId].data = recordData;
    emit Updated(msg.sender, recordId);
    return true;
  }

  // once a record has been deleted from the idList it can no longer be modified, but the memory remains
  // You can still pull deleted records if you hit the Table object directly, you will not be able to use GetByIndex or GetById
  function Delete(address recordId) public returns(bool success) {
    require(Exists(recordId));
    // get the record id to delete
    uint recordToDelete = Table[recordId].idListPointer;
    // set the last item in the id list to keep and move
    address keyToMove = IdList[IdList.length - 1];
    // replace the id of the deleted record with the one we want to keep i.e the last item
    IdList[recordToDelete] = keyToMove;
    // update the last record in the list to point to it's new position in the key list which is the old deleted records spot
    Table[keyToMove].idListPointer = recordToDelete;
    // remove the last element from the id list that holds the old pointer for the keep record
    IdList.length--;
    // emit event
    emit Deleted(msg.sender, recordId);
    return true;
  }

}

在函数参数中使用结构可以显着减少合约的混乱和复杂性,同时使它们交互起来更加愉快。

英文原文:https://medium.com/@dillonsvincent/solidity-enable-experimental-abiencoderv2-to-use-a-struct-as-function-parameter-27979603a879

注意

外部函数 不可以接受多维数组作为参数
如果原文件加入 pragma abicoder v2; 可以启用ABI v2版编码功能,这此功能可用。 (注:在 0.7.0 之前是使用pragma experimental ABIEncoderV2;

内部函数 则不需要启用ABI v2 就接受多维数组作为参数。
参考自:https://learnblockchain.cn/docs/solidity/contracts.html

以太坊合约方法参数的一些注意项

  1. 一个方法的参数最多有16个
    Solidity 允许 16 个参数。参考链接,EVM 不是寄存器机而是堆栈机,因此所有计算都在称为堆栈的数据区域上执行。它的最大大小为 1024 个元素并包含 256 位的字。通过以下方式对堆栈的访问仅限于顶端:可以将最顶端的 16 个元素之一复制到堆栈的顶部,或者将最顶端的元素与它下面的 16 个元素之一交换。

  2. 可以使用结构体作为参数
    https://ethereum.stackexchange.com/questions/65980/passing-struct-as-an-argument-in-call
    https://medium.com/@dillonsvincent/solidity-enable-experimental-abiencoderv2-to-use-a-struct-as-function-parameter-27979603a879

持续总结

参考链接

https://ethereum.stackexchange.com/questions/5945/how-many-arguments-can-solidity-take

以太坊中的叔块(uncle block)

孤块(orphan block)

在比特币协议中,最长的链被认为是绝对的正确。如果一个块不是最长链的一部分,那么它被称为是“孤块”。一个孤立的块是一个块,它也是合法的,但是发现的稍晚,或者是网络传输稍慢,而没有能成为最长的链的一部分。在比特币中,孤块没有意义,随后将被抛弃,发现这个孤块的矿工也拿不到采矿相关的奖励。

最重的链(heaviest)

Ethereum的GHOST协议,不认为孤块没有价值,而是会给与发现孤块的矿工以回报。在以太坊中,孤块被称为“叔块”(uncle block),它们可以为主链的安全作出贡献。

相对来说,比特币有很长的块间隔时间。在比特币区块中,平均约10分钟可以得到一个确认(也就是发现一个新的后续区块)。但是自从比特币成立以来,大量关于块链技术的研究已经发展起来。这些研究表明,更短的块间隔确实是可能的,而且在很多应用场景下是需要的。然而,随着拥有更快的出块速度,孤块的增加而带来的昂贵的成本和浪费也随之增加。

GHOST协议支付报酬给叔块,这激励了矿工在新发现的块中去引用叔块。引用叔块使主链更重。在比特币,最长的链是主链。在以太坊中,主链是指最重的链。

叔块的好处

解决了两个问题:

  1. 以太坊十几秒的出块间隔,大大增加了孤块的产生,并且降低了安全性。通过鼓励引用叔块,使引用主链获得更多的安全保证(因为孤块本身也是合法的)
  2. 比特币中,采矿中心化(大量的集中矿池)成为一个问题。给与叔块报酬,可以一定程度上缓解这个问题。

叔块的引用

区块可以不引用,或者最多引用两个叔块
叔块必须是区块的前2层~前7层的祖先的直接的子块
被引用过的叔块不能重复引用
引用叔块的区块,可以获得挖矿报酬的1/32,也就是51/32=0.15625 Ether。最多获得20.15625=0.3125 Ether
被引用的叔块,其矿工的报酬和叔块与区块之间的间隔层数有关系。

间隔层数 报酬比例 报酬(ether)
1 7/8 4.375
2 6/8 3.75
3 5/8 3.125
4 4/8 2.5
5 3/8 1.875
6 2/8 1.25

原文链接:https://blog.csdn.net/superswords/article/details/76445278

以太坊矿工出块的完整过程

创建矿工

miner/worker.go

func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool, init bool) *worker {
 ...
worker.wg.Add(4)
    go worker.mainLoop()
    go worker.newWorkLoop(recommit)
    go worker.resultLoop()
    go worker.taskLoop()

接收任务

// newWorkLoop is a standalone goroutine to submit new mining work upon received events.
func (w *worker) newWorkLoop(recommit time.Duration) {
...
// commit aborts in-flight transaction execution with given signal and resubmits a new one.
    commit := func(noempty bool, s int32) {
        if interrupt != nil {
            atomic.StoreInt32(interrupt, s)
        }
        interrupt = new(int32)
        select {
        case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}: //接收任务

任务传递

// mainLoop is a standalone goroutine to regenerate the sealing task based on the received event.
func (w *worker) mainLoop() {
....

    for {
        select {
        case req := <-w.newWorkCh:
            w.commitNewWork(req.interrupt, req.noempty, req.timestamp) // 提交任务

开始提交

// commitNewWork generates several new sealing tasks based on the parent block.
func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
    ...
    w.commit(uncles, w.fullTaskHook, true, tstart) // 开始提交

组装区块

func (w *worker) commit(uncles []*types.Header, interval func(), update bool, start time.Time) error {
...
    block, receipts, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, txs, uncles, cpyReceipts) // 通过对应共识组装最后区块
    if err != nil {
        return err
    }
    if w.isRunning() {
        if interval != nil {
            interval()
        }
        select {
        case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}: //开始广播区块
func (w *worker) taskLoop() {
     ...
        if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil { // 开始矿工签名
                log.Warn("Block sealing failed", "err", err)
                w.pendingMu.Lock()
                delete(w.pendingTasks, sealHash)
                w.pendingMu.Unlock()
            }

矿工签名

func (c *Congress) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
// Sign all the things!
    sighash, err := signFn(accounts.Account{Address: val}, accounts.MimetypeCongress, CongressRLP(header))
    if err != nil {
        return err
    }
    copy(header.Extra[len(header.Extra)-extraSeal:], sighash) //将签名放到区块header.Extra字段

    // Wait until sealing is terminated or delay timeout.
    log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
    go func() {
        select {
        case <-stop:
            return
        case <-time.After(delay):
        }

        select {
        case results <- block.WithSeal(header): // 返回给上一步的 w.resultCh
        default:
            log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header))
        }
    }()

广播区块

func (w *worker) resultLoop() {
    defer w.wg.Done()
    for {
        select {
        case block := <-w.resultCh: // 接收到上步矿工签名的区块
        ...
        // Commit block and state to database.
        _, err := w.chain.WriteBlockWithState(block, receipts, logs, task.state, true) // 更新本地数据
        ...
        // Broadcast the block and announce chain insertion event
            w.mux.Post(core.NewMinedBlockEvent{Block: block}) // 广播区块

            // Insert the block into the set of pending ones to resultLoop for confirmations
            w.unconfirmed.Insert(block.NumberU64(), block.Hash())