区块链研习社区块链 Ripple

Ripple共识过程中的各种View

2018-06-20  本文已影响9人  SwordShield

一条交易从发出到落链的过程

  1. 客户端通过ws,http发出签名/未签名的交易
  2. 节点接收到交易并检查签名
  3. 节点程序本地走一遍对交易的验证,如果通过,广播并投赞成票,如果不通过,将错误返给客户端
  4. 交易被广播后,其它节点同样走验证过程,并对交易投票
  5. 交易最终超过一定百分比(这个随时间增加会增大)的赞成票,则进入交易候选集,否则,下一次共识继续投票
  6. 进入交易集的交易会在生成区块时进行应用,应用的过程也是走本地验证,如果能成功apply,则被包含进本地区块,否则,进入到下一个OpenView中去apply
  7. 本地生成区块后广播区块hash,达到一定百分比的区块hash会共识通过,与这一hash不同的,向其它节点获取区块内容

在上面的过程中,Ripple是通过什么机制保证交易应用过程的原子性(就是交易可能前半部分改变了状态,后半部分失败了,这时候要全部回滚)呢,在以太坊中有快照,交易执行不成功可以回滚,比特币只有一个转账交易,比较简单,本身可以认为是原子操作。

Ripple中的View

Ripple中

基类有:RawView,ReadView,ApplyView,

中间类:TxsRawView,DigestAwareReadView,ApplyViewBase

实现类:OpenView,Ledger,ApplyViewImpl

继承关系:

TxsRawView : Rawview

DigestAwareReadView : ReadView

ApplyViewBase : ApplyView

OpenView : TxsRawView,ReadView

Ledger : TxsRawView,DigestAwareReadView

ApplyViewImpl : ApplyViewBase

RawView的作用:

Interface for ledger entry changes.

Subclasses allow raw modification of ledger entries.

其中的方法都可以无条件修改SLE

ReadView:

A view into a ledger.

This interface provides read access to state
and transaction items. There is no checkpointing
or calculation of metadata.

Ripped中通过ApplyView来完成沙盒的功能

先看一段代码:

RCLConsensus::Adaptor::buildLCL
{
    ...
    // Build the new last closed ledger
    auto buildLCL =
        std::make_shared<Ledger>(*previousLedger.ledger_, closeTime);
    ...
    {
        OpenView accum(&*buildLCL);
        assert(!accum.open());
        if (replay)
        {
            // Special case, we are replaying a ledger close
            for (auto& tx : replay->txns_)
                applyTransaction(
                    app_, accum, *tx.second, false, tapNO_CHECK_SIGN, j_);
        }
        else
        {
            // Normal case, we are not replaying a ledger close
            retriableTxs = applyTransactions(
                app_, set, accum, [&buildLCL](uint256 const& txID) {
                    return !buildLCL->txExists(txID);
                });
        }
        // appy to Ledger
        accum.apply(*buildLCL);
    }
    ...
}

这是共识过程中,生成要广播的区块的代码,里面的buildLCL就是要生成的区块,可以看到,这中间的过程是这样的:

  1. 先根据buildLCL生成一个OpenView
  2. 将交易应用到OpenView对象中,去改变SLE状态,并收集真正能成功应用的交易
  3. OpenView对象反过来应用到buildLCL

过程3对应的代码:

void
OpenView::apply (TxsRawView& to) const
{
    // apply sle change
    items_.apply(to);
    
    // insert txs
    for (auto const& item : txs_)
        to.rawTxInsert (item.first,
            item.second.first,
                item.second.second);
}

这里的to就是上面的buildLCL,items_.apply(to); 会改变状态树SHAMap,to.rawTxInsert会改变交易树SHAMap,最终两个树根hash会参与到LedgerHash的计算中。

上面的代码可以说是用OpenView来过滤了一次交易,将侯选集中的交易再次应用一遍,然后把结果 应用到Ledger中,那么,如何保证OpenView中操作的原子性呢?

这就要看具体的验证过程了applyTransaction会调到apply.cpp中的apply方法,这是一个全局方法,定义如下:

std::pair<TER, bool>
apply (Application& app, OpenView& view,
    STTx const& tx, ApplyFlags flags,
        beast::Journal j)
{
    STAmountSO saved(view.info().parentCloseTime);
    auto pfresult = preflight(app, view.rules(), tx, flags, j);
    auto pcresult = preclaim(pfresult, app, view);
    return doApply(pcresult, app, view);
}

这跟交易刚发到节点时进行的本地验证其实是一个过程。

在这个过程中preflight与preclaim都是检查工作,最终会改变sle的是doApply操作

std::pair<TER, bool>
doApply(PreclaimResult const& preclaimResult,
    Application& app, OpenView& view)
{
    if (preclaimResult.view.seq() != view.seq())
    {
        // Logic error from the caller. Don't have enough
        // info to recover.
        return{ tefEXCEPTION, false };
    }
    try
    {
        if (!preclaimResult.likelyToClaimFee)
            return{ preclaimResult.ter, false };
        ApplyContext ctx(app, view,
            preclaimResult.tx, preclaimResult.ter,
                preclaimResult.baseFee, preclaimResult.flags,
                    preclaimResult.j);
        return invoke_apply(ctx);
    }
    catch (std::exception const& e)
    {
        JLOG(preclaimResult.j.fatal()) <<
            "apply: " << e.what();
        return { tefEXCEPTION, false };
    }
}

在doApply中用到了ApplyContext,这个类比较关键,可以看到它的构造函数传入了openview对象,构造函数如下 :

ApplyContext::ApplyContext(Application& app_,
    OpenView& base, STTx const& tx_, TER preclaimResult_,
        std::uint64_t baseFee_, ApplyFlags flags,
            beast::Journal journal_)
    : app(app_)
    , tx(tx_)
    , preclaimResult(preclaimResult_)
    , baseFee(baseFee_)
    , journal(journal_)
    , base_ (base)
    , flags_(flags)
{
    view_.emplace(&base_, flags_);
}

private:
boost::optional<ApplyViewImpl> view_;

ApplyContext中的view是根据openview对象构造出来的ApplyViewImpl类型的对象,在doApply执行过程中,用到的都是这个view_对象,就是这个view_起到了关键作用,因为最终是要改变openView才能生效,改变OpenView是在交易完全共识过才进行的:

static
std::pair<TER, bool>
invoke_apply (ApplyContext& ctx)
{
   switch(ctx.tx.getTxnType())
    {
    case ttACCOUNT_SET:     { SetAccount    p(ctx); return p(); }
    case ttOFFER_CANCEL:    { CancelOffer   p(ctx); return p(); }
    case ttOFFER_CREATE:    { CreateOffer   p(ctx); return p(); }
    case ttPAYMENT:         { Payment       p(ctx); return p(); }
    ...
}

//每种交易类都是继承自Transactor类的
std::pair<TER, bool>
Transactor::operator()()
{
    ...
    auto terResult = ctx_.preclaimResult;
    if (terResult == tesSUCCESS)
        terResult = apply();    //里面调用各交易类的doApply方法
        
    bool didApply = isTesSuccess (terResult);
    
    if (didApply)
    {
        // Transaction succeeded fully or (retries are
        // not allowed and the transaction could claim a fee)

       ...
        //真正改变OpenView的是这行代码
        ctx_.apply(terResult);
        // since we called apply(), it is not okay to look
        // at view() past this point.
    }
}

TER Transactor::apply ()
{
    preCompute();

    ...
    //调用各个子类的doApply方法
    return doApply();
}

//ApplyViewImpl的内容应用到OpenView上
void
ApplyContext::apply(TER ter)
{
    view_->apply(base_, tx, ter, journal);
}

也就是说,只有交易类的doApply方法真的完全成功了,结果才会反映到OpenView中,OpenView再将结果转移到Ledger中,Ripple中就是用这种方式对交易进行原子式的应用,不会出现交易前面修改了SLE,后面又失败了,导致SLE的修改不可逆的情况。

上一篇 下一篇

猜你喜欢

热点阅读