EOS开发者日志2017 07-9

DQmZWjRzX696igR6tq7QEgGfQeMdHVfJTgfWrqzzD8R9mVt_1680x8400.png

原文地址:https://steemit.com/eos/@dan/eos-developer-s-log-stardate-201707-9
今天我想花点时间解释一下当前eos.io转账的结构,以便开发者能更好地理解货币模型。下面是一个转账信息的json文件,这是sam转账给alice的信息。在这个例子中,货币(currency),sam,alice是帐号名称,但是将会以不同的方式使用这些账号。

{
  "refBlockNum": "12",
  "refBlockPrefix": "2792049106",
  "expiration": "2015-05-15T14:29:01",
  "scope": [
    "alice",
    "sam"
  ],
  "messages": [
    {
      "code": "currency",
      "type": "transfer",
      "recipients": [
        "sam",
        "alice"
      ],
      "authorization": [
        {
          "account": "sam",
          "permission": "active"
        }
      ],
      "data": "a34a59dcc8000000c9251a0000000000501a00000000000008454f53000000000568656c6c6f"
    }
  ],
  "signatures": []
}

当使用签名把这些信息序列化为二进制的时候,这个转账信息的大小大概160 byte,比steem的转账信息略大一些,steem的转账信息大小大概120 byte,bitshares的转账信息的大小 大概94 byte。之所以数据变大了,是因为包含了一些特定的收据,验证和范围信息,这些信息大约51 byte。

把转账当作权益证明(TaPoS – Transactions as Proof of Stake)

熟悉steem 和bitshares的人可能会认出这个转账信息的头三个字段;它们都是一样的。这些字段被TaPoS(把转账当作权益证明Transactions as Proof of Stake)使用,确保只能在引用区块之后和到期之前计入转账信息。

scope

下一个字段”scope”,是新加入eos.io的,用于指定读取或写入的数据的范围。如果一条消息尝试在范围之外读或写数据,那么这个交易就会失败。我们可以并行地处理交易,只要在它们的范围内没有重叠。

eos.io软件的一项关键创新是scope 和 代码(code)是两个完全分开的概念。你可以看到即使我们使用货币合约的代码(code)来执行转账,货币合约也不会涉及到scope。

消息

一次转账可以包含一条或多条按次序并自动申请的消息(要么全部成功要么全部失败)。在本例中只有一条消息,让我们仔细看看:

代码:

每条消息必须指定它要执行的代码,在本例中,货币合约的代码将会调用下面的方法:

currency::apply_currency_transfer(data)
类型:

类型(type)字段定义了消息的类型(并隐式地指定了数据data的格式)。从面向对象的角度来看的话,你可以把type看作“currency”类上的“name”方法。在本例中,类型是“transfer”

${namespace}::apply_${code}_${type}( data )

本例中,“namespace”是货币合约,当然,方法apply_currency_transfer 也可以在其它的命名空间(namespace)中被使用。

接收者:

除了调用currency::apply_currency_transfer(data)之外,方法apply_currency_transfer(data)也会被每条收据调用。比如,下面的方法会以这种次序顺序调用:

currency::apply_currency_transfer(data)
alice::apply_currency_transfer(data)
sam::apply_currency_transfer(data)

account::标记了实现这个方法的合约。alice 和sam可以选择不实现这个方法,如果当currency::apply_currency_transfer被执行的时候,它们没有什么特别的逻辑需要执行的话。然而,如果sam是一个交易所,那么sam在货币转账生成的时候,它可能会去处理存款与提款。

生成这笔转账的人可以添加任意数量的接收者。而且某些合约可以要求一定要通知合约的某些参与方。

在currency这个例子中,发送者和接收者都必须通知。你可以在这查看它们是如何被指定的

void apply_currency_transfer() {
   const auto& transfer  = currentMessage<Transfer>();
   requireNotice( transfer.to, transfer.from );
   ...
}
验证

每条消息都可以对一个或多个帐号要求验证。在steem和bitshares中,验证是基于消息类型隐式定义的;但是在eos.io,消息必须显示地定义提供的验证。eos.io系统会自动的验证,此交易是否已被所需要的所有签名所签署,以此授予特定的权限。

在本例中,消息表明了它必须被sam的活动授权层级(active permission level)签署。合约代码将验证sam的授权是否已提供。你可以在这个示例中查看验证

void apply_currency_transfer() {
   const auto& transfer  = currentMessage<Transfer>();
   requireNotice( transfer.to, transfer.from );
   requireAuth( transfer.from );
   ...
}
数据:

每个合约都可以定义它自己的数据格式。不使用abi的话,这些数据只能被转换为十六进制数据;然而,货币合约定义了数据的格式为transfer结构:

struct Transfer {
  AccountName from;
  AccountName to;
  uint64_t    amount = 0;
};

根据这个定义,我们可以把那些二进制数据转换为类似下面的这种数据:

{ "from" : "sam",  "to": "alice",  "amount": 100 }

调度

现在我们理解了eos.io转账的结构,我们就可以看看eos.io区块的结构了。每个区块会被分成环(cycle),环都是顺序执行的。在每个环的内部,可以有任意数量的并行执行的线程。这么做的奥妙在于可以确保线程包含的转账的范围不会交叉。如果一个环内的线程之间存在范围(scope)重叠的话,不需要额外的数据处理就能把该区块声明为无效的。

结论

并行执行的最大挑战在于确保同一数据不会被两个线程同时获取。与传统并行编程不同的是,我们不太可能使用锁来限制读取,因为这么做会增加不确定性,而且可能会打破共识。即使能使用锁,我们也不太愿意使用,因为频繁使用锁可能会降低性能。

一种替代的方法是,与其在获取数据的时候使用锁,不如在调度执行的时候使用锁。从这个角度来看,scope字段定义了转账想要加锁的账号。调度者(区块生产者)确保线程不会同时获取同一个锁。使用这种转账结构和调度机制,才能解决内存获取冲突以及提高并行执行的机会。

与其它平台不同,把代码(货币合约)从数据(账号存储)中分离开来,使得锁的分离成为可能。如果货币合约和它的数据是绑定的,那么每次转账就必须给货币合约上锁,那么所有的转账将会被单线程的吞吐量所限制。但是由于转账消息只需要在发送者和接收者的数据上锁,那么货币合约将不再是瓶颈。

对于交易所合约来说,货币合约是不是瓶颈就尤其重要。每次交易所取款或提款时,货币合约和交易所合约都会被强制并入一个线程。如果频繁地使用这两个合约,那么就会降低所有货币和所有交易所用户的性能。

在eos.io模式下,两个用户间可以自由转账,而不需要担心其它账号/合约的顺序(单线程的)吞吐量。