以太坊源码分析(11)eth目前的共识算法pow的整理

### eth目前的共识算法pow的整理

##### 涉及的代码子包主要有consensus,miner,core,geth

```
/consensus 共识算法
   consensus.go
1. Prepare方法
2. CalcDifficulty方法:计算工作量
3. AccumulateRewards方法:计算每个块的出块奖励
4. VerifySeal方法:校验pow的工作量难度是否符合要求,返回nil则通过
5. verifyHeader方法:校验区块头是否符合共识规则
```



/miner 挖矿
work.go
commitNewWork():提交新的块,新的交易,从交易池中获取未打包的交易,然后提交交易,进行打包
__核心代码__:
```
// Create the current work task and check any fork transitions needed
    work := self.current
    if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {
        misc.ApplyDAOHardFork(work.state)
    }
    pending, err := self.eth.TxPool().Pending()
    if err != nil {
        log.Error("Failed to fetch pending transactions", "err", err)
        return
    }
    txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
    work.commitTransactions(self.mux, txs, self.chain, self.coinbase)

```



```
eth/handler.go
    NewProtocolManager --> verifyHeader --> VerifySeal

```

__整条链的运行,打包交易,出块的流程__
```
/cmd/geth/main.go/main
    makeFullNode-->RegisterEthService-->eth.New-->NewProtocolManager --> verifyHeader --> VerifySeal

```

##### eth的共识算法pow调用栈详解


###### 核心的逻辑需要从/eth/handler.go/NewProtocolManager方法下开始,关键代码:
```
manager.downloader = downloader.New(mode, chaindb, manager.eventMux, blockchain, nil, manager.removePeer)

    validator := func(header *types.Header) error {
        return engine.VerifyHeader(blockchain, header, true)
    }
    heighter := func() uint64 {
        return blockchain.CurrentBlock().NumberU64()
    }
    inserter := func(blocks types.Blocks) (int, error) {
        // If fast sync is running, deny importing weird blocks
        if atomic.LoadUint32(&manager.fastSync) == 1 {
            log.Warn("Discarded bad propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
            return 0, nil
        }
        atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
        return manager.blockchain.InsertChain(blocks)
    }
    manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)

    return manager, nil
```

###### 该方法中生成了一个校验区块头部的对象 `validator`
###### 让我们继续跟进 `engine.VerifyHeader(blockchain, header, true)` 方法:

```
// VerifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum ethash engine.
func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
    // If we're running a full engine faking, accept any input as valid
    if ethash.fakeFull {
        return nil
    }
    // Short circuit if the header is known, or it's parent not
    number := header.Number.Uint64()
    if chain.GetHeader(header.Hash(), number) != nil {
        return nil
    }
    parent := chain.GetHeader(header.ParentHash, number-1)
    if parent == nil {
        return consensus.ErrUnknownAncestor
    }
    // Sanity checks passed, do a proper verification
    return ethash.verifyHeader(chain, header, parent, false, seal)
}

```

###### 首先看第一个if,它的逻辑判断是如果为true,那么就关闭所有的共识规则校验,紧跟着两个if判断是只要该block的header的hash和number或者上一个header的hash和number存在链上,那么它header的共识规则校验就通过,如果都不通过,那么该区块校验失败跑出错误.如果走到最后一个return,那么就需要做一些其他额外验证

```
// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum ethash engine.
// See YP section 4.3.4. "Block Header Validity"
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
    // Ensure that the header's extra-data section is of a reasonable size
    if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
        return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
    }
    // Verify the header's timestamp
    if uncle {
        if header.Time.Cmp(math.MaxBig256) > 0 {
            return errLargeBlockTime
        }
    } else {
        if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 {
            return consensus.ErrFutureBlock
        }
    }
    if header.Time.Cmp(parent.Time) <= 0 {
        return errZeroBlockTime
    }
    // Verify the block's difficulty based in it's timestamp and parent's difficulty
    expected := CalcDifficulty(chain.Config(), header.Time.Uint64(), parent)
    if expected.Cmp(header.Difficulty) != 0 {
        return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
    }
    // Verify that the gas limit is <= 2^63-1
    if header.GasLimit.Cmp(math.MaxBig63) > 0 {
        return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, math.MaxBig63)
    }
    // Verify that the gasUsed is <= gasLimit
    if header.GasUsed.Cmp(header.GasLimit) > 0 {
        return fmt.Errorf("invalid gasUsed: have %v, gasLimit %v", header.GasUsed, header.GasLimit)
    }

    // Verify that the gas limit remains within allowed bounds
    diff := new(big.Int).Set(parent.GasLimit)
    diff = diff.Sub(diff, header.GasLimit)
    diff.Abs(diff)

    limit := new(big.Int).Set(parent.GasLimit)
    limit = limit.Div(limit, params.GasLimitBoundDivisor)

    if diff.Cmp(limit) >= 0 || header.GasLimit.Cmp(params.MinGasLimit) < 0 {
        return fmt.Errorf("invalid gas limit: have %v, want %v += %v", header.GasLimit, parent.GasLimit, limit)
    }
    // Verify that the block number is parent's +1
    if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
        return consensus.ErrInvalidNumber
    }
    // Verify the engine specific seal securing the block
    if seal {
        if err := ethash.VerifySeal(chain, header); err != nil {
            return err
        }
    }
    // If all checks passed, validate any special fields for hard forks
    if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
        return err
    }
    if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
        return err
    }
    return nil
}

```
###### 该方法会去校验该区块头中的extra数据是不是比约定的参数最大值还大,如果超过,则返回错误,其次会去判断该区块是不是一个uncle区块,如果header的时间戳不符合规则则返回错误,然后根据链的配置,header的时间戳以及上一个区块计算中本次区块期待的难度,如果header的难度和期待的不一致,或header的gasLimit比最大数字还大,或已用的gas超过gasLimit,则返回错误.如果gasLimit超过预定的最大值或最小值,或header的number减去上一个block的header的number不为1,则返回错误. `seal` 为true,则会去校验该区块是否符合pow的工作量证明的要求( `verifySeal` 方法),因为私有链一般是不需要pow.最后两个if是去判断区块是否具有正确的hash,防止用户在不同的链上,以及校验块头的额外数据字段是否符合DAO硬叉规则

###### uncle block:
__eth允许旷工在挖到一个块的同时包含一组uncle block列表,主要有两个作用:__
1. 由于网络传播的延迟原因,通过奖励那些由于不是链组成区块部分而产生陈旧或孤立区块的旷工,从而减少集权激励
2. 通过增加在主链上的工作量来增加链条的安全性(在工作中,少浪费工作在陈旧的块上)

eth的pow核心代码:

```
// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
// TODO (karalabe): Move the chain maker into this package and make this private!
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
    next := new(big.Int).Add(parent.Number, big1)
    switch {
    case config.IsByzantium(next):
        return calcDifficultyByzantium(time, parent)
    case config.IsHomestead(next):
        return calcDifficultyHomestead(time, parent)
    default:
        return calcDifficultyFrontier(time, parent)
    }
}
```
###### 该方法的第一个 `case` 是根据拜占庭规则去计算新块应该具有的难度,第二个 `case` 是根据宅基地规则去计算新块应该具有的难度,第三个 `case` 是根据边界规则去计算难度


#### pow计算生成新块代码解析
__/consensus/seal.go/seal__

```
// Seal implements consensus.Engine, attempting to find a nonce that satisfies
// the block's difficulty requirements.
func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
   // If we're running a fake PoW, simply return a 0 nonce immediately
   if ethash.fakeMode {
       header := block.Header()
       header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
       return block.WithSeal(header), nil
   }
   // If we're running a shared PoW, delegate sealing to it
   if ethash.shared != nil {
       return ethash.shared.Seal(chain, block, stop)
   }
   // Create a runner and the multiple search threads it directs
   abort := make(chan struct{})
   found := make(chan *types.Block)

   ethash.lock.Lock()
   threads := ethash.threads
   if ethash.rand == nil {
       seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
       if err != nil {
           ethash.lock.Unlock()
           return nil, err
       }
       ethash.rand = rand.New(rand.NewSource(seed.Int64()))
   }
   ethash.lock.Unlock()
   if threads == 0 {
       threads = runtime.NumCPU()
   }
   if threads < 0 {
       threads = 0 // Allows disabling local mining without extra logic around local/remote
   }
   var pend sync.WaitGroup
   for i := 0; i < threads; i++ {
       pend.Add(1)
       go func(id int, nonce uint64) {
           defer pend.Done()
           ethash.mine(block, id, nonce, abort, found)
       }(i, uint64(ethash.rand.Int63()))
   }
   // Wait until sealing is terminated or a nonce is found
   var result *types.Block
   select {
   case <-stop:
       // Outside abort, stop all miner threads
       close(abort)
   case result = <-found:
       // One of the threads found a block, abort all others
       close(abort)
   case <-ethash.update:
       // Thread count was changed on user request, restart
       close(abort)
       pend.Wait()
       return ethash.Seal(chain, block, stop)
   }
   // Wait for all miners to terminate and return the block
   pend.Wait()
   return result, nil
}

```

###### 该方法的foreach中并行挖新块,一旦停止或者找到新快,则废弃其他所有的,如果协程计算有变更,则重新调用方法

###### 好了pow挖矿的核心方法已经出现,`ethash.mine`,如果挖取到新块,那么就写入到 `found channel`

```
// mine is the actual proof-of-work miner that searches for a nonce starting from
// seed that results in correct final block difficulty.
func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {
    // Extract some data from the header
    var (
        header = block.Header()
        hash = header.HashNoNonce().Bytes()
        target = new(big.Int).Div(maxUint256, header.Difficulty)

        number = header.Number.Uint64()
        dataset = ethash.dataset(number)
    )
    // Start generating random nonces until we abort or find a good one
    var (
        attempts = int64(0)
        nonce = seed
    )
    logger := log.New("miner", id)
    logger.Trace("Started ethash search for new nonces", "seed", seed)
    for {
        select {
        case <-abort:
            // Mining terminated, update stats and abort
            logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed)
            ethash.hashrate.Mark(attempts)
            return

        default:
            // We don't have to update hash rate on every nonce, so update after after 2^X nonces
            attempts++
            if (attempts % (1 << 15)) == 0 {
                ethash.hashrate.Mark(attempts)
                attempts = 0
            }
            // Compute the PoW value of this nonce
            digest, result := hashimotoFull(dataset, hash, nonce)
            if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
                // Correct nonce found, create a new header with it
                header = types.CopyHeader(header)
                header.Nonce = types.EncodeNonce(nonce)
                header.MixDigest = common.BytesToHash(digest)

                // Seal and return a block (if still needed)
                select {
                case found <- block.WithSeal(header):
                    logger.Trace("Ethash nonce found and reported", "attempts", nonce-seed, "nonce", nonce)
                case <-abort:
                    logger.Trace("Ethash nonce found but discarded", "attempts", nonce-seed, "nonce", nonce)
                }
                return
            }
            nonce++
        }
    }
}


```

###### `target` 是目标新块的难度, `hashimotoFull` 方法计算出一个hash值,如果产生的 `hash` 值小于等于 `target` 的值,则hash难度符合要求,将符合要求的 `header` 写入到 `found channel` 中,并返回,否则一直循环

```
// hashimotoFull aggregates data from the full dataset (using the full in-memory
// dataset) in order to produce our final value for a particular header hash and
// nonce.
func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) {
    lookup := func(index uint32) []uint32 {
        offset := index * hashWords
        return dataset[offset : offset+hashWords]
    }
    return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup)
}

```

```
// hashimoto aggregates data from the full dataset in order to produce our final
// value for a particular header hash and nonce.
func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) {
    // Calculate the number of theoretical rows (we use one buffer nonetheless)
    rows := uint32(size / mixBytes)

    // Combine header+nonce into a 64 byte seed
    seed := make([]byte, 40)
    copy(seed, hash)
    binary.LittleEndian.PutUint64(seed[32:], nonce)

    seed = crypto.Keccak512(seed)
    seedHead := binary.LittleEndian.Uint32(seed)

    // Start the mix with replicated seed
    mix := make([]uint32, mixBytes/4)
    for i := 0; i < len(mix); i++ {
        mix[i] = binary.LittleEndian.Uint32(seed[i%16*4:])
    }
    // Mix in random dataset nodes
    temp := make([]uint32, len(mix))

    for i := 0; i < loopAccesses; i++ {
        parent := fnv(uint32(i)^seedHead, mix[i%len(mix)]) % rows
        for j := uint32(0); j < mixBytes/hashBytes; j++ {
            copy(temp[j*hashWords:], lookup(2*parent+j))
        }
        fnvHash(mix, temp)
    }
    // Compress mix
    for i := 0; i < len(mix); i += 4 {
        mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
    }
    mix = mix[:len(mix)/4]

    digest := make([]byte, common.HashLength)
    for i, val := range mix {
        binary.LittleEndian.PutUint32(digest[i*4:], val)
    }
    return digest, crypto.Keccak256(append(seed, digest...))
}

```

###### 可以看到,该方法是不断的进行sha256的hash运算,然后返回进行hash值难度的对比,如果hash的十六进制大小小于等于预定的难度,那么这个hash就是符合产块要求的

产生一个随机seed,赋给nonce随机数,
然后进行sha256的hash运算,如果算出的hash难度不符合目标难度,则nonce+1,继续运算



worker.go/wait()
func (self *worker) wait() {
    for {
        mustCommitNewWork := true
        for result := range self.recv {
            atomic.AddInt32(&self.atWork, -1)

            if result == nil {
                continue
            }


agent.go/mine()方法


func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
    if result, err := self.engine.Seal(self.chain, work.Block, stop); result != nil {
        log.Info("Successfully sealed new block", "number", result.Number(), "hash", result.Hash())
        self.returnCh <- &Result{work, result}
    } else {
        if err != nil {
            log.Warn("Block sealing failed", "err", err)
        }
        self.returnCh <- nil
    }
}

如果挖到一个新块,则将结果写到self的return管道中


写块的方法WriteBlockAndState

wait方法接收self.recv管道的结果,如果有结果,说明本地挖到新块了,则将新块进行存储,
并把该块放到待确认的block判定区

miner.go/update方法,如果有新块出来,则停止挖矿进行下载同步新块,如果下载完成或失败的事件,则继续开始挖矿.



/geth/main.go/geth --> makeFullNode --> utils.RegisterEthService

--> eth.New(ctx, cfg) --> miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)


这个是启动链后到挖矿,共识代码的整个调用栈,开始分析核心方法


func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine) *Miner {
    miner := &Miner{
        eth: eth,
        mux: mux,
        engine: engine,
        worker: newWorker(config, engine, common.Address{}, eth, mux),
        canStart: 1,
    }
    miner.Register(NewCpuAgent(eth.BlockChain(), engine))
    go miner.update()

    return miner
}

从miner.Update()的逻辑可以看出,对于任何一个Ethereum网络中的节点来说,挖掘一个新区块和从其他节点下载、同步一个新区块,根本是相互冲突的。这样的规定,保证了在某个节点上,一个新区块只可能有一种来源,这可以大大降低可能出现的区块冲突,并避免全网中计算资源的浪费。

首先是:

func (self *Miner) Register(agent Agent) {
    if self.Mining() {
        agent.Start()
    }
    self.worker.register(agent)
}

func (self *worker) register(agent Agent) {
    self.mu.Lock()
    defer self.mu.Unlock()
    self.agents[agent] = struct{}{}
    agent.SetReturnCh(self.recv)
}

该方法中将self的recv管道绑定在了agent的return管道


然后是newWorker方法

func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
    worker := &worker{
        config: config,
        engine: engine,
        eth: eth,
        mux: mux,
        txCh: make(chan core.TxPreEvent, txChanSize),
        chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
        chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
        chainDb: eth.ChainDb(),
        recv: make(chan *Result, resultQueueSize),
        chain: eth.BlockChain(),
        proc: eth.BlockChain().Validator(),
        possibleUncles: make(map[common.Hash]*types.Block),
        coinbase: coinbase,
        agents: make(map[Agent]struct{}),
        unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
    }
    // Subscribe TxPreEvent for tx pool
    worker.txSub = eth.TxPool().SubscribeTxPreEvent(worker.txCh)
    // Subscribe events for blockchain
    worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
    worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
    go worker.update()

    go worker.wait()
    worker.commitNewWork()

    return worker
}


该方法中绑定了三个管道,额外启动了两个goroutine执行update和wait方法,
func (self *worker) update() {
    defer self.txSub.Unsubscribe()
    defer self.chainHeadSub.Unsubscribe()
    defer self.chainSideSub.Unsubscribe()

    for {
        // A real event arrived, process interesting content
        select {
        // Handle ChainHeadEvent
        case <-self.chainHeadCh:
            self.commitNewWork()

        // Handle ChainSideEvent
        case ev := <-self.chainSideCh:
            self.uncleMu.Lock()
            self.possibleUncles[ev.Block.Hash()] = ev.Block
            self.uncleMu.Unlock()

        // Handle TxPreEvent
        case ev := <-self.txCh:
            // Apply transaction to the pending state if we're not mining
            if atomic.LoadInt32(&self.mining) == 0 {
                self.currentMu.Lock()
                acc, _ := types.Sender(self.current.signer, ev.Tx)
                txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
                txset := ty
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以太坊是一个平台,它上面提供各种模块让用户来搭建应用,如果将搭建应用比作造房子,那么以太坊就提供了墙面、屋顶、地板等模块,用户只需像搭积木一样把房子搭起来,因此在以太坊上建立应用的成本和速度都大大改善。具体来说,以太坊通过一套图灵完备的脚本语言(Ethereum Virtual Machinecode,简称EVM语言)来建立应用,它类似于汇编语言。我们知道,直接用汇编语言编程是非常痛苦的,但以太坊里的编程并不需要直接使用EVM语言,而是类似C语言、Python、Lisp等高级语言,再通过编译器转成EVM语言。上面所说的平台之上的应用,其实就是合约,这是以太坊的核心。合约是一个活在以太坊系统里的自动代理人,他有一个自己的以太币地址,当用户向合约的地址里发送一笔交易后,该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果,这个结果可能是从合约的地址发出另外一笔交易。需要指出的是,以太坊中的交易,不单只是发送以太币而已,它还可以嵌入相当多的额外信息。如果一笔交易是发送给合约的,那么这些信息就非常重要,因为合约将根据这些信息来完成自身的业务逻辑。合约所能提供的业务,几乎是无穷无尽的,它的边界就是你的想象力,因为图灵完备的语言提供了完整的自由度,让用户搭建各种应用。白皮书举了几个例子,如储蓄账户、用户自定义的子货币等。 2013年年末,以太坊创始人Vitalik Buterin发布了以太坊初版白皮书,启动了项目。2014年7月24日起,以太坊进行了为期42天的以太币预售。2016年初,以太坊的技术得到市场认可,价格开始暴涨,吸引了大量开发者以外的人进入以太坊的世界。中国三大比特币交易所之二的火币网及OKCoin币行都于2017年5月31日正式上线以太坊。 [1] 自从进入2016年以来,那些密切关注数字货币产业的人都急切地观察着第二代加密货币平台以太坊的发展动向。作为一种比较新的利用比特币技术的开发项目,以太坊致力于实施全球去中心化且无所有权的的数字技术计算机来执行点对点合约。简单来说就是,以太坊是一个你无法关闭的世界计算机。加密架构与图灵完整性的创新型结合可以促进大量的新产业的出现。反过来,传统行业的创新压力越来越大,甚至面临淘汰的风险。比特币网络事实上是一套分布式的数据库,而以太坊则更进一步,她可以看作是一台分布式的计算机:区块链是计算机的ROM,合约是程序,而以太坊的矿工们则负责计算,担任CPU的角色。这台计算机不是、也不可能是免费使用的,不然任何人都可以往里面存储各种垃圾信息和执行各种鸡毛蒜皮的计算,使用它至少需要支付计算费和存储费,当然还有其它一些费用。最为知名的是2017年初以摩根大通、芝加哥交易所集团、纽约梅隆银行、汤森路透、微软、英特尔、埃森哲等20多家全球top金融机构和科技公司成立的企业以太坊联盟。而以太坊催生的加密货币以太币近期又成了继比特币之后受追捧的资产。  智能合约的潜在应用很多。彭博社商业周刊称它是“所有人共享但无法篡改的软件”。更高级的软件有可能用以太坊创建网络商店。区块链程序以太坊可以用来创建去中心化的程序、自治组织和智能合约,据纽约时报的报导,在2016年5月已经有数十个可用的程序。预期的应用目标涵盖金融、物联网、农田到餐桌(farm-to-table)、智能电网、体育,菠菜等。去中心化自治组织有潜力让许多原本无法运行或成本过高的营运模型成为可能。较知名的应用有:去中心化创业投资:The DAO用以太币资金创立,目标是为商企业和非营利机构创建新的去中心化营业模式、The Rudimental让独立艺术家在区块链上进行群众募资。社会经济平台:Backfeed。去中心化预测市场:Augur。物联网:Ethcore(一间以太坊公司)研发的客户端、Chronicled(一间区块链公司)发表了以太坊区块链的实物资产验证平台;芯片公司、物理IP创建者和生产者可以用植入的蓝牙或近场通信进行验证。Slock.It开发的智能锁可以在付费后自动打开,让用户在付费后可以帮电动车充电、或是打开租屋的房门。虚拟宝物交易平台:FreeMyVunk。版权授权:Ujo Music平台让创作人用智能合约发布音乐,消费者可以直接付费给创作人。伊莫珍·希普用此平台发布了一首单曲。智能电网:TransActive Grid让用户可以和邻居买卖能。去中心化期权市场:Etheropt。钉住汇率的代币:DigixDAO提供与黄金挂钩的代币,在2016年四月正式营运。Decentralized Capital提供和各种货币挂钩的代币。移动支付:Everex让外劳汇款回家乡。客户端软件以太坊的两个主要的客户端软件是Geth和Parity。企业软件企业软件公司也正测试用以太坊作为各种用途。已知有兴趣的公司包括微软、IBM、摩根大通。德勤和ConsenSys在2016年
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值