vtk删除一个actor_如何构建一个基于actor的简单区块链_cumi7754的博客-程序员ITS304

技术标签: 区块链  python  java  数据库  分布式  

vtk删除一个actor

Scalachain is a blockchain built using the Scala programming language and the actor model (Akka Framework).

Scalachain是使用Scala编程语言和参与者模型( Akka Framework )构建的区块链。

In this story I will show the development process to build this simple prototype of a blockchain. This means that the project is not perfect, and there may be better implementations. For all these reasons any contribution — may it be a suggestion, or a PR on the GitHub repository — is very welcome! :-)

在这个故事中,我将展示构建此简单区块链原型的开发过程。 这意味着该项目并不完美,并且可能会有更好的实现。 由于所有这些原因,我们非常欢迎您提供任何意见(包括建议或GitHub 存储库上的PR)! :-)

Let’s start with a little introduction to the blockchain. After that we can define the simplified model that we will implement.

让我们从对区块链的一些介绍开始。 之后,我们可以定义将要实现的简化模型。

区块链快速入门 (Quick Introduction to the blockchain)

There a lot of good articles that explain how a blockchain works, so I will do a high level introduction just to provide some context to this project.

有很多很好的文章解释了区块链的工作原理,因此我将做一个高层次的介绍,只是为该项目提供一些背景信息。

The blockchain is a distributed ledger: it registers some transaction of values (like coins) between a sender and a receiver. What makes a blockchain different from a traditional database is the decentralized nature of the blockchain: it is distributed among several communicating nodes that guarantee the validity of the transactions registered.

区块链是一种分布式账本 :它在发送方和接收方之间注册一些价值交易(如硬币)。 区块链与传统数据库的不同之处在于区块链的分散性:它分布在多个通信节点之间,以保证注册交易的有效性。

The blockchain stores transactions in blocks, that are created —we say mined — by nodes investing computational power. Every block is created by solving a cryptographic puzzle that is hard to solve, but easy to verify. In this way, every block represents the work needed to solve such puzzle. This is the reason why the cryptographic puzzle is called the Proof of Work: the solution of the puzzle is the proof that a node spent a certain amount of work to solve it and mine the block.

区块链将交易存储在区块中,区块是由投资计算能力的节点创建的(我们说是开采的) 。 每个区块都是通过解决难以解决但易于验证的密码难题创建的。 这样,每个方块代表解决此类难题所需的工作。 这就是密码难题被称为工作量证明的原因:难题的解决方案是节点花费大量工作来解决它和挖掘区块的证明。

Why do nodes invest computational power to mine a block? Because the creation of a new block is rewarded by a predefined amount of coins. In this way nodes are encouraged to mine new blocks, contributing in the growth and strength of the blockchain.

为什么节点要投入计算能力来挖掘一块? 因为新块的创建将获得预定义数量的硬币奖励。 通过这种方式,鼓励节点挖掘新区块,为区块链的增长和实力做出贡献。

The solution of the Proof Of Work depends on the values stored in the last mined block. In this way every block is chained to the previous one. This means that, to change a mined block, a node should mine again all the blocks above the modified one. Since every block represents an amount of work, this operation would be unfeasible once several blocks are mined upon the modified one. This is the foundation of the distributed consensus, The agreement of all the nodes on the validity of the blocks (that is the transactions) stored in the blockchain.

工作量证明的解决方案取决于存储在最后一个开采区块中的值。 这样,每个块都链接到前一个块。 这意味着,要更改已开采的区块,节点应再次开采已修改区块上方的所有区块。 由于每个块代表大量的工作,因此一旦在修改后的块中挖掘了几个块,此操作将是不可行的。 这是分布式 共识的基础,所有节点对区块链中存储的区块(即交易)有效性的共识

It may happen that different nodes mine a block at the same time, creating different “branches” from the same blockchain — this is called a fork in the blockchain. This situation is solved when a branch becomes longer than the others: the longest chain always wins, so the winning branch becomes the new blockchain.

可能会发生不同的节点同时挖掘一个区块,从而从同一区块链创建不同的“分支”的情况-这在区块链中称为分叉 。 当分支变得比其他分支更长时,这种情况就解决了:最长的链总是获胜,因此获胜的分支成为新的区块链。

区块链模型 (The blockchain model)

Scalachain is based on a blockchain model that is a simplification of the Bitcoin one.

Scalachain基于一种区块链模型,该模型简化了比特币。

The main components of our blockchain model are the Transaction, the Chain, the Proof of Work (PoW) algorithm, and the Node. The transactions are stored inside the blocks of the chain, that are mined using the PoW. The node is the server that runs the blockchain.

我们的区块链模型的主要组件是交易,链,工作量证明(PoW)算法和节点。 事务存储在链的各个块中,这些块使用PoW进行挖掘。 节点是运行区块链的服务器。

Transaction

交易

Transactions register the movement of coins between two entities. Every transaction is composed by a sender, a recipient, and an amount of coin. Transactions will be registered inside the blocks of our blockchain.

交易记录了两个实体之间硬币的移动。 每笔交易都由发送者,接收者和一定数量的硬币组成。 交易将被注册在我们的区块链中。

Chain

The chain is a linked list of blocks containing a list of transactions. Every block of the chain has an index, the proof that validates it (more on this later), the list of transactions, the hash of the previous block, the list of previous blocks, and a timestamp. Every block is chained to the previous one by its hash, that is computed converting the block to a JSON string and then hashing it through a SHA-256 hashing function.

链是包含事务列表的块的链接列表。 链中的每个区块都有一个索引,对其进行验证的证明(稍后会详细介绍),事务列表,上一个区块的哈希,上一个区块的列表以及时间戳。 每个块都通过其哈希值链接到前一个块,该哈希值是将块转换为JSON字符串,然后通过SHA-256哈希函数对其进行哈希计算。

PoW

工作量

The PoW algorithm is required to mine the blocks composing the blockchain. The idea is to solve a cryptographic puzzle that is hard to solve, but easy to verify having the proof. The PoW algorithm that is implemented in Scalachain is similar to the Bitcoin one (based on Hashcash). It consists in finding a hash with N leading zeros, that is computed starting from the hash of the last block and a number, that is the proof of our algorithm.

需要PoW算法来挖掘组成区块链的区块。 这个想法是要解决一个密码难题,这个难题很难解决,但是容易验证有证据。 Scalachain中实现的PoW算法类似于比特币一种(基于Hashcash )。 它包括找到一个具有N个前导零的哈希,该哈希从最后一个块的哈希和一个数字开始计算,这是我们算法的证明。

We can formalize it as:

我们可以将其形式化为:

NzerosHash = SHA-256(previousNodeHash + proof)

The higher is N, the harder is to find the proof. In Scalachain N=4 (It will be configurable eventually).

N越高,找到证明就越难。 在Scalachain中,N = 4(最终将可配置)。

Node

节点

The Node is the server running our blockchain. It provides some REST API to interact with it and perform basic operations such as send a new transaction, get the list of pending transactions, mine a block, and get the current status of the blockchain.

节点是运行我们的区块链的服务器。 它提供了一些REST API与之交互并执行基本操作,例如发送新交易,获取待处理交易列表,挖掘区块并获取区块链的当前状态。

Scala中的区块链实施 (Blockchain implementation in Scala)

We are going to implement the defined model using the Scala Programming Language. From an high level view, the things we need to implement a blockchain are:

我们将使用Scala编程语言实现定义的模型。 从高级的角度来看,我们实现区块链所需要做的事情是:

  • transactions

    交易
  • the chain of blocks containing lists of transactions

    包含交易清单的区块链
  • the PoW algorithm to mine new blocks

    PoW算法来挖掘新块

These components are the essential parts of a blockchain.

这些组件是区块链的基本组成部分。

Transaction

交易

The transaction is a very simple object: it has a sender, a recipient and a value. We can implement it as a simple case class.

事务是一个非常简单的对象:它有一个发送者,一个接收者和一个值。 我们可以将其实现为简单的case class

case class Transaction(sender: String, recipient: String, value: Long)

Chain

The chain is the core of our blockchain: it is a linked list of blocks containing transactions.

链是我们区块链的核心:它是包含交易的区块的链表。

sealed trait Chain {

  val index: Int
  val hash: String
  val values: List[Transaction]
  val proof: Long
  val timestamp: Long
}

We start by creating a sealed trait that represents the block of our chain. The Chain can have two types: it can be an EmptyChain or a ChainLink. The former is our block zero (the genesis block), and it is implemented as a singleton (it is a case object), while the latter is a regular mined block.

我们从创建一个sealed trait开始,该sealed trait代表了我们的区块链。 Chain可以有两种类型:它可以是EmptyChainChainLink 。 前者是我们的零区块( 创世区块 ),它被实现为一个单例(它是一个case object ),而后者是一个常规的开采区块。

case class ChainLink(index: Int, proof: Long, values: List[Transaction], previousHash: String = "", tail: Chain = EmptyChain, timestamp: Long = System.currentTimeMillis()) extends Chain {
  val hash = Crypto.sha256Hash(this.toJson.toString)
}

case object EmptyChain extends Chain {
  val index = 0
  val hash = "1"
  val values = Nil
  val proof = 100L
  val timestamp = System.currentTimeMillis()
}

Let’s look more in detail at our chain. It provides an index, that is the current height of the blockchain. There is the list of Transaction, the proof that validated the block, and the timestamp of the block creation. The hash value is set to a default one in the EmptyChain, while in the ChainLink it is computed converting the object to its JSON representation and hashing it with an utility function (see the crypto package in the repository). The ChainLink provides also the hash of the previous block in the chain (our link between blocks). The tail field is a reference to the previously mined blocks. This may not be the most efficient solution, but it is useful to see how the blockchain grows in our simplified implementation.

让我们详细了解一下我们的链。 它提供了一个索引,即区块链的当前高度。 这里有Transaction清单,验证区块的证明以及区块创建的时间戳。 哈希值在EmptyChain设置为默认值,而在ChainLink ,将其计算为将对象转换为其JSON表示并使用实用程序函数对其进行哈希处理(请参见存储库中crypto包)。 ChainLink还提供链中上一个块(我们的块之间的链接)的哈希。 尾部字段是对先前开采的区块的引用。 这可能不是最有效的解决方案,但是了解简化的实现中区块链的增长方式很有用。

We can improve our Chain with some utilities. We can add it a companion object that defines an apply method to create a new chain passing it a list of blocks. A companion object is like a “set of static methods” — doing an analogy with Java — that has complete access rights on the fields and methods of the class/trait.

我们可以使用一些实用程序来改进我们的Chain 。 我们可以为它添加一个伴随对象 ,该对象定义了apply方法,以创建一个新链,将链列表传递给它。 伴随对象就像一个“静态方法集”(类似于Java),它具有对类/特征的字段和方法的完全访问权限。

object Chain {
  def apply[T](b: Chain*): Chain = {
    if (b.isEmpty) EmptyChain
    else {
      val link = b.head.asInstanceOf[ChainLink]
      ChainLink(link.index, link.proof, link.values, link.previousHash, apply(b.tail: _*))
    }
  }
}

If the list of blocks is empty, we simply initialize our blockchain with an EmptyChain. Otherwise we create a new ChainLink adding as a tail the result of the apply method on the remaining blocks of the list. In this way the list of blocks is added following the order of the list.

如果块列表为空,则只需使用EmptyChain初始化我们的EmptyChain链。 否则,我们将创建一个新的ChainLink作为尾部,在列表的其余块上添加apply方法的结果。 这样,将按照列表的顺序添加块列表。

It would be nice to have the possibility to add a new block to our chain using a simple addition operator, like the one we have on List. We can define our own addition operator :: inside the Chain trait.

能够使用一个简单的加法运算符(如List上的加法运算符)将新块添加到我们的链中,将是很好的。 我们可以在Chain特征中定义自己的加法运算符::

sealed trait Chain {

  val index: Int
  val hash: String
  val values: List[Transaction]
  val proof: Long
  val timestamp: Long

  def ::(link: Chain): Chain = link match {
    case l:ChainLink => ChainLink(l.index, l.proof, l.values, this.hash, this)
    case _ => throw new InvalidParameterException("Cannot add invalid link to chain")
  }
}

We pattern match on the block that is passed as an argument: if it is a valid ChainLink object we add it as the head of our chain, putting the chain as the tail of the new block, otherwise we throw an exception.

我们对作为参数传递的块进行模式匹配:如果它是有效的ChainLink对象,则将其添加为链的头部,将链作为新块的尾部,否则抛出异常。

PoW

工作量

The PoW algorithm is fundamental for the mining of new blocks. We implement it as a simple algorithm:

PoW算法是挖掘新块的基础。 我们将其实现为一个简单的算法:

  1. Take the hash of the last block and a number representing the proof.

    取最后一块的哈希值和代表证明的数字。

2. Concatenate the hash and the proof in a string.

2.将哈希和证明连接在字符串中。

3. hash the resulting string using the SHA-256 algorithm.

3.使用SHA-256算法对所得字符串进行哈希处理。

4. check the 4 leading characters of the hash: if they are four zeros return the proof.

4.检查哈希的4个前导字符:如果它们是四个零,则返回证明。

5. otherwise repeat the algorithm increasing the proof by one.

5.否则重复算法,将证明加一。

This a simplification of the HashCash algorithm used in the Bitcoin blockchain.

这是比特币区块链中使用的HashCash算法的简化。

Since it is a recursive function, we can implement it as a tail recursive one to improve the usage of resources.

由于它是一种递归函数,因此我们可以将其实现为尾递归函数,以提高资源利用率。

object ProofOfWork {

  def proofOfWork(lastHash: String): Long = {
    @tailrec
    def powHelper(lastHash: String, proof: Long): Long = {
      if (validProof(lastHash, proof))
        proof
      else
        powHelper(lastHash, proof + 1)
    }

    val proof = 0
    powHelper(lastHash, proof)
  }

  def validProof(lastHash: String, proof: Long): Boolean = {
    val guess = (lastHash ++ proof.toString).toJson.toString
    val guessHash = Crypto.sha256Hash(guess)
    (guessHash take 4) == "0000"
  }
}

The validProof function is used to check if the proof we are testing is the correct one. The powHelper function is a helper function that executes our loop using tail recursion, increasing the proof at each step. The proofOfWork function wrap all the things up, and is exposed by the ProofOfWork object.

validProof函数用于检查我们正在测试的证明是否正确。 powHelper函数是一个辅助函数,它使用尾部递归执行我们的循环,从而增加了每一步的证明。 proofOfWork函数将所有内容包装起来,并由ProofOfWork对象公开。

演员模型 (The actor model)

The actor model is a programming model designed for concurrent processing, scalability, and fault tolerance. The model defines the atomic elements that compose the software systems — the actors — and the way this elements interact between them. In this project we will use the actor model implemented in Scala by the Akka Framework.

actor模型是一种为并行处理可伸缩性容错能力设计的编程模型。 该模型定义了构成软件系统的原子元素- 参与者 -以及这些元素之间的交互方式。 在这个项目中,我们将使用由Akka Framework在Scala中实现的actor模型。

Actor

演员

The actor is the atomic unit of the actor model. it is a computational unit that can send and receive messages. Every actor has an internal private state and a mailbox. When an actor receives and compute a message, it can react in 3 ways:

角色是角色模型的原子单位。 它是可以发送和接收消息的计算单元。 每个参与者都有一个内部私有状态和一个邮箱。 当一个参与者接收并计算一条消息时,它可以通过三种方式做出React:

  • Send a message to another actor.

    向其他演员发送消息。
  • Change its internal state.

    更改其内部状态。
  • Create another actor.

    创建另一个演员。

Communication is asynchronous, and messages are popped out from the mailbox and processed in series. To enable the parallel computation of messages you need to create several actors. Many actors together crate an actor system. The behavior of the application arises from the interaction between actors providing different functionalities.

通信是异步的 ,并且消息从邮箱弹出并按顺序处理。 要启用消息的并行计算,您需要创建多个参与者。 许多演员共同创建一个演员系统 。 应用程序的行为源自提供不同功能的参与者之间的交互。

Actors are independent

演员是独立的

Actors are independent one to another, and they do not share their internal state. This fact has a couple of important consequences:

演员彼此独立,他们没有内部状态。 这个事实有两个重要的后果:

1. Actors can process messages without side-effects one to another.

1.演员可以处理没有副作用的消息。

2. It’s not important where an actor is — be it your laptop, a sever, or in the cloud — once we know its address we can request its services sending it a message.

2.演员所在的位置(无论是您的笔记本电脑,服务器还是云)都不重要,一旦我们知道了演员的地址,便可以请求其发送消息的服务。

The first point makes concurrent computation very easy. We can be sure that the processing of a message will not interfere with the processing of another one. To achieve concurrent processing we can deploy several actors able to process the same kind of message.

第一点使并发计算非常容易。 我们可以确保处理一条消息不会干扰另一条消息的处理。 为了实现并发处理,我们可以部署几个能够处理相同类型消息的参与者。

The second point is all about scalability: we need more computational power? No problem: we can start a new machine and deploy new actors that will join the existing actor system. Their mailbox addresses will be discoverable by existing actors, that will start communicate with them.

第二点是关于可伸缩性的 :我们需要更多的计算能力吗? 没问题:我们可以启动一台新机器并部署新角色,这些角色将加入现有角色系统。 现有参与者可以发现他们的邮箱地址,并开始与他们进行通信。

Actors are supervised

演员受到监督

As we said in the description of the actor, one of the possible reaction to a message is the creation of other actors. When this happens, the father becomes the supervisor of its children. If a children fails, the supervisor can decide the action to take, may it be create a new actor, ignore the failure, or throw it up to its own supervisor. In this way the Actor System becomes a hierarchy tree, each node supervising its children. This is the way the actor model provides fault tolerance.

正如我们在演员描述中所说的那样,对消息的可能React之一就是创建其他演员。 发生这种情况时,父亲变成了孩子们的导师 。 如果孩子失败了,监督者可以决定要采取的行动,可以是创建新的演员,忽略失败,还是将其交给自己的监督者。 这样,Actor系统就变成了一个层次树,每个节点都在监督其子节点。 这就是参与者模型提供容错能力的方式

经纪人,一个简单的演员 (Broker, a simple actor)

The first actor we are going to implement is the Broker Actor: it is the manager of the transactions of our blockchain. Its responsibilities are the addition of new transactions, and the retrieval of pending ones.

我们要实施的第一个参与者是经纪人参与者:它是我们区块链交易的管理者。 它的职责是添加新交易以及检索未决交易。

The Broker Actor reacts to three kind of messages, defined in the companion object of the Broker class:

Broker Actor对在Broker类的companion object中定义的三种消息作出React:

object Broker {
  sealed trait BrokerMessage
  case class AddTransaction(transaction: Transaction) extends BrokerMessage
  case object GetTransactions extends BrokerMessage
  case object Clear extends BrokerMessage

  val props: Props = Props(new Broker)
}

We create a trait BrokerMessage to identify the messages of the Broker Actor. Every other message will extend this trait. AddTransaction adds a new transaction to the list of pending ones. GetTransaction retrieve the pending transactions, and Clear empties the list. The props value is used to initialize the actor when it will be created.

我们创建一个特征BrokerMessage来标识Broker Actor的消息。 其他所有消息都将扩展此特性。 AddTransaction将新事务添加到挂起的事务列表中。 GetTransaction检索挂起的事务,而Clear清空列表。 props值用于在创建actor时对其进行初始化。

class Broker extends Actor with ActorLogging {
  import Broker._

  var pending: List[Transaction] = List()

  override def receive: Receive = {
    case AddTransaction(transaction) => {
      pending = transaction :: pending
      log.info(s"Added $transaction to pending Transaction")
    }
    case GetTransactions => {
      log.info(s"Getting pending transactions")
      sender() ! pending
    }
    case Clear => {
      pending = List()
      log.info("Clear pending transaction List")
    }
  }
}

The Broker class contains the business logic to react to the different messages. I won’t go into the details because it is trivial. The most interesting thing is how we respond to a request of the pending transactions. We send them to the sender() of the GetTransaction message using the tell (!) operator. This operator means “send the message and don’t wait for a response” — aka fire-and-forget.

Broker class包含对不同消息做出React的业务逻辑。 我将不赘述,因为它是微不足道的。 最有趣的是我们如何响应未决交易的请求。 我们使用tell ( ! )运算符将它们sender()GetTransaction消息的sender() 。 此运算符的意思是“发送消息,不要等待响应”,又名即发即弃。

矿工,不同州的演员 (Miner, an actor with different states)

The Miner Actor is the one mining new blocks for our blockchain. Since we don’t want mine a new block while we are mining another one, the Miner Actor will have two states: ready, when it is ready to mine a new block, and busy, when it is mining a block.

矿工演员是为我们的区块链挖掘新区块的人之一。 由于我们不希望在挖掘另一个区块时挖掘一个新区块,因此矿工Actor将具有两种状态: ready ,准备挖掘一个新区块的状态和busy ,挖掘一个区块的状态。

Let’s start by defining the companion object with the messages of the Miner Actor. The pattern is the same, with a sealed trait — MinerMessage — used to define the kind of messages this actor reacts to.

让我们开始定义带有矿工演员消息的companion object 。 模式是相同的,具有密封的特征MinerMessage ,用于定义该MinerMessage的消息的类型。

object Miner {
  sealed trait MinerMessage
  case class Validate(hash: String, proof: Long) extends MinerMessage
  case class Mine(hash: String) extends MinerMessage
  case object Ready extends MinerMessage

  val props: Props = Props(new Miner)
}

The Validate message asks for a validation of a proof, and pass to the Miner the hash and the proof to check. Since this component is the one interacting with the PoW algorithm, it is its duty to execute this check. The Mine message asks for the mining starting from a specified hash. The last message, Ready, triggers a state transition.

Validate消息要求验证证明,并将哈希值和证明传递给矿工进行检查。 由于该组件是与PoW算法交互的组件,因此执行此检查是其职责。 Mine消息要求从指定的哈希开始进行挖掘。 最后一条消息Ready触发状态转换。

Same actor, different states

同一演员,不同州

The peculiarity of this actor is that it reacts to the messages according to its state: busy or ready. Let’s analyze the difference in the behavior:

这个actor的独特之处在于,它根据消息的状态( busyready对消息做出React。 让我们分析一下行为上的区别:

  • busy: the Miner is busy mining a block. If a new mining request comes, it should deny it. If it is requested to be ready, the Miner should change its state to the ready one.

    繁忙 :矿工正在忙于开采一个街区。 如果有新的采矿请求,则应拒绝。 如果要求准备就绪,则矿工应将其状态更改为就绪状态。

  • ready: the Miner is idle. If a mining request come, it should start mining a new block. If it is requested to be ready, it should say: “OK, I’m ready!”

    准备好 :矿工闲置。 如果出现挖掘请求,则应开始挖掘新块。 如果要求准备就绪,则应该说:“好,我准备好了!”

  • both: the Miner should be always available to verify the correctness of a proof, both in a ready or busy state.

    两者 :矿工在准备就绪或繁忙状态下应始终可用以验证证明的正确性。

Time so see how we can implement this logic in our code. We start by defining the common behavior, the validation of a proof.

时间到了,看看如何在代码中实现此逻辑。 我们首先定义常见行为,即证明的有效性。

We define a function validate that reacts to the Validate message: if the proof is valid we respond to the sender with a success, otherwise with a failure. The ready and the busy states are defined as functions that “extends” the validate one, since that is a behavior we want in both states.

我们定义了一个功能validate ,它对Validate消息做出React:如果证明有效,我们以成功的方式响应发送方,否则以失败的方式响应。 ready状态和busy状态被定义为“扩展” validate状态的功能,因为这是我们在两种状态下都想要的行为。

def validate: Receive = {
    case Validate(hash, proof) => {
      log.info(s"Validating proof $proof")
      if (ProofOfWork.validProof(hash, proof)){
        log.info("proof is valid!")
        sender() ! Success
      }
      else{
        log.info("proof is not valid")
        sender() ! Failure(new InvalidProofException(hash, proof))
      }
    }
  }

A couple of things to highlight here.

这里有两点要强调。

1. The state transition is triggered using the become function, provided by the Akka Framework. This takes as an argument a function that returns a Receive object, like the ones we defined for the validation, busy, and ready state.

1.使用Akka Framework提供的become功能触发状态转换。 该函数将返回Receive对象的函数作为参数,就像我们为validationbusyready状态定义的函数一样。

2. When a mining request is received by the Miner, it responds with a Future containing the execution of the PoW algorithm. In this way we can work asynchronously, making the Miner free to do other tasks, such as the validation one.

2.当矿工收到挖掘请求时,它将以包含有执行PoW算法的Future响应。 这样,我们可以异步工作,使矿工可以自由地执行其他任务,例如验证任务。

3. The supervisor of this Actor controls the state transition. The reason of this choice is that the Miner is agnostic about the state of the system. It doesn’t know when the mining computation in the Future will be completed, and it can’t know if the block that it is mining has been already mined from another node. This would require to stop mining the current hash, and start mining the hash of the new block.

3.该Actor的主管控制状态转换。 这种选择的原因是,矿工对系统状态不了解。 它不知道Future的挖掘计算何时完成,也不知道它正在挖掘的块是否已经从另一个节点中挖掘出来。 这将需要停止挖掘当前的哈希,并开始挖掘新块的哈希。

The last thing is to provide an initial state overriding the receive function.

最后一件事是提供一个覆盖receive功能的初始状态。

override def receive: Receive = {
    case Ready => become(ready)
  }

We start waiting for a Ready message. When it comes, we start our Miner.

我们开始等待Ready消息。 当它来的时候,我们开始我们的矿工。

区块链,一个持久的参与者 (Blockchain, a persistent actor)

The Blockchain Actor interacts with the business logic of the blockchain. It can add a new block to the blockchain, and it can retrieve information about the state of the blockchain. This actor has another superpower: it can persist and recover the state of the blockchain. This is possible implementing the PersistentActor trait provided by the Akka Framework.

区块链参与者与区块链的业务逻辑进行交互。 它可以向区块链添加一个新块,并且可以检索有关区块链状态的信息。 这个参与者还有另一个超级大国:它可以持久并恢复区块链的状态。 可以实现Akka框架提供的PersistentActor特性。

object Blockchain {
  sealed trait BlockchainEvent
  case class AddBlockEvent(transactions: List[Transaction], proof: Long) extends BlockchainEvent

  sealed trait BlockchainCommand
  case class AddBlockCommand(transactions: List[Transaction], proof: Long) extends BlockchainCommand
  case object GetChain extends BlockchainCommand
  case object GetLastHash extends BlockchainCommand
  case object GetLastIndex extends BlockchainCommand

  case class State(chain: Chain)

  def props(chain: Chain, nodeId: String): Props = Props(new Blockchain(chain, nodeId))
}
view raw

We can see that the companion object of this actor has more elements than the other ones. The State class is where we store the state of our blockchain, that is its Chain. The idea is to update the state every time a new block is created.

我们可以看到该companion object具有比其他参与者更多的元素。 State类是我们存储区块链状态(即Chain 。 想法是每次创建新块时都更新状态。

For this purpose, there are two different traits: BlockchainEvent and BlockchainCommand. The former is to handle the events that will trigger the persistence logic, the latter is used to send direct commands to the actor. The AddBlockEvent message is the event that will update our state. The AddBlockCommand, GetChain, GetLastHash, and LastIndex commands are the one used to interact with the underlying blockchain.

为此,有两个不同的特征: BlockchainEventBlockchainCommand 。 前者用于处理将触发持久性逻辑的事件,后者用于将直接命令发送给参与者。 AddBlockEvent消息是将更新我们状态的事件。 AddBlockCommandGetChainGetLastHashLastIndex命令是用于与基础区块链进行交互的命令。

The usual props function initializes the Blockchain Actor with the initial Chain and the nodeId of the Scalachain node.

常用的props函数使用初始Chain和Scalachain节点的nodeId初始化Blockchain Actor。

class Blockchain(chain: Chain, nodeId: String) extends PersistentActor with ActorLogging{
  import Blockchain._

  var state = State(chain)

  override def persistenceId: String = s"chainer-$nodeId"
  
  //Code...
}

The Blockchain Actor extends the trait PersistentActor provided by the Akka framework. In this way we have out-of-the-box all the logic required to persist and recover our state.

区块链演员扩展了Akka框架提供的特征PersistentActor 。 这样,我们就可以开箱即用地保存和恢复状态所需的所有逻辑。

We initialize the state using the Chain provided as an argument upon creation. The nodeId is part of the persistenceId that we override. The persistence logic will use it to identify the persisted state. Since we can have multiple Scalachain nodes running in the same machine, we need this value to correctly persist and recover the state of each node.

我们使用创建时作为参数提供的Chain初始化状态。 nodeId是我们覆盖的persistenceId一部分。 持久性逻辑将使用它来识别持久状态。 由于我们可以在同一台机器上运行多个Scalachain节点,因此我们需要此值才能正确保留并恢复每个节点的状态。

def updateState(event: BlockchainEvent) = event match {
    case AddBlockEvent(transactions, proof) =>
      {
        state = State(ChainLink(state.chain.index + 1, proof, transactions) :: state.chain)
        log.info(s"Added block ${state.chain.index} containing ${transactions.size} transactions")
      }
  }

The updateState function executes the update of the Actor state when the AddBlockEvent is received.

收到AddBlockEvent时, updateState函数将执行Actor状态的更新。

override def receiveRecover: Receive = {
    case SnapshotOffer(metadata, snapshot: State) => {
      log.info(s"Recovering from snapshot ${metadata.sequenceNr} at block ${snapshot.chain.index}")
      state = snapshot
    }
    case RecoveryCompleted => log.info("Recovery completed")
    case evt: AddBlockEvent => updateState(evt)
  }

The receiveRecover function reacts to the recovery messages sent by the persistence logic. During the creation of an actor a persisted state (snapshot) may be offered to it using the SnapshotOffer message. In this case the current state becomes the one provided by the snapshot.

receiveRecover函数对持久性逻辑发送的恢复消息做出React。 在创建actor期间,可以使用SnapshotOffer消息向其提供持久状态( 快照 )。 在这种情况下,当前状态变为快照提供的状态。

RecoveryCompleted message informs us that the recovery process completed successfully. The AddBlockEvent triggers the updateState function passing the event itself.

RecoveryCompleted消息通知我们恢复过程已成功完成。 AddBlockEvent触发updateState函数传递事件本身。

override def receiveCommand: Receive = {
    case SaveSnapshotSuccess(metadata) => log.info(s"Snapshot ${metadata.sequenceNr} saved successfully")
    case SaveSnapshotFailure(metadata, reason) => log.error(s"Error saving snapshot ${metadata.sequenceNr}: ${reason.getMessage}")
    case AddBlockCommand(transactions : List[Transaction], proof: Long) => {
      persist(AddBlockEvent(transactions, proof)) {event =>
        updateState(event)
      }

      // This is a workaround to wait until the state is persisted
      deferAsync(Nil) { _ =>
        saveSnapshot(state)
        sender() ! state.chain.index
      }
    }
    case AddBlockCommand(_, _) => log.error("invalid add block command")
    case GetChain => sender() ! state.chain
    case GetLastHash => sender() ! state.chain.hash
    case GetLastIndex => sender() ! state.chain.index
  }

The receiveCommand function is used to react to the direct commands sent to the actor. Let’s skip the GetChain, GetLastHash, and GetLastIndex commands, since they are trivial. The AddBlockCommand is the interesting part: it creates and fires an AddBlock event, that is persisted in the event journal of the Actor. In this way events can be replayed in case of recovery.

receiveCommand函数用于对发送给角色的直接命令做出React。 让我们跳过GetChainGetLastHashGetLastIndex命令,因为它们很简单。 AddBlockCommand是有趣的部分:它创建并触发一个AddBlock事件,该事件将AddBlock在Actor的事件日志中。 这样,在恢复的情况下可以重播事件。

The deferAsync function waits until the state is updated after the processing of the event. Once the event has been executed the actor can save the snapshot of the state, and inform the sender of the message with the updated last index of the Chain. The SaveSnapshotSucces and SaveSnapshotFailure messages helps us to keep track of possible failures.

deferAsync函数将等待,直到事件处理后状态被更新为止。 一旦执行了事件,参与者就可以保存状态的快照,并使用Chain的更新后的最后索引将消息通知给发件人。 SaveSnapshotSuccesSaveSnapshotFailure消息有助于我们跟踪可能的故障。

节点,一个演员来统治他们 (Node, an actor to rule them all)

The Node Actor is the backbone of our Scalachain node. It is the supervisor of all the other actors (Broker, Miner, and Blockchain), and the one communicating with the outside world through the REST API.

Node Actor是我们Scalachain节点的骨干。 它是所有其他参与者(经纪人,矿工和区块链)的主管 ,也是通过REST API与外界通信的人。

object Node {

  sealed trait NodeMessage

  case class AddTransaction(transaction: Transaction) extends NodeMessage

  case class CheckPowSolution(solution: Long) extends NodeMessage

  case class AddBlock(proof: Long) extends NodeMessage

  case object GetTransactions extends NodeMessage

  case object Mine extends NodeMessage

  case object StopMining extends NodeMessage

  case object GetStatus extends NodeMessage

  case object GetLastBlockIndex extends NodeMessage

  case object GetLastBlockHash extends NodeMessage

  def props(nodeId: String): Props = Props(new Node(nodeId))

  def createCoinbaseTransaction(nodeId: String) = Transaction("coinbase", nodeId, 100)
}

The Node Actor has to handle all the high level messages that coming from the REST API. This is the reason why we find in the companion object more or less the same messages we implemented in the children actors. The props function takes a nodeId as an argument to create our Node Actor. This will be the one used for the initialization of Blockchain Actor. The createCoinbaseTransaction simply creates a transaction assigning a predefined coin amount to the node itself. This will be the reward for the successful mining of a new block of the blockchain.

Node Actor必须处理来自REST API的所有高级消息。 这就是为什么我们在companion object或多或少地发现在子actor中实现的相同消息的原因。 props函数将nodeId作为参数来创建我们的Node Actor。 这将是用于初始化Blockchain Actor的工具。 createCoinbaseTransaction只是创建一个将预定义硬币数量分配给节点本身的交易。 这将是成功挖掘区块链新区块的奖励

class Node(nodeId: String) extends Actor with ActorLogging {

  import Node._

  implicit lazy val timeout = Timeout(5.seconds)

  val broker = context.actorOf(Broker.props)
  val miner = context.actorOf(Miner.props)
  val blockchain = context.actorOf(Blockchain.props(EmptyChain, nodeId))

  miner ! Ready
  
  //Code...
}

Let’s look at the initialization of the Node Actor. The timeout value is used by the ask (?) operator (this will be explained shortly). All our actors are created in the actor context, using the props function we defined in each actor.

让我们看一下Node Actor的初始化。 超时值由ask ( ? )运算符使用(稍后将对此进行说明)。 我们所有的参与者都是在参与者context中使用我们在每个参与者中定义的props函数创建的。

The Blockchain Actor is initialized with the EmptyChain and the nodeId of the Node. Once everything is created, we inform the Miner Actor to be ready to mine sending it a Ready message. Ok, we are now ready to receive some message and react to it.

使用EmptyChain和节点的nodeId初始化Blockchain Actor。 创建完所有内容后,我们会通知矿工演员准备好向其发送Ready消息。 好的,我们现在准备接收一些消息并对它做出React。

override def receive: Receive = {
    case AddTransaction(transaction) => {
      //Code...
    }
    case CheckPowSolution(solution) => {
      //Code...
    }
    case AddBlock(proof) => {
      //Code...
    }
    case Mine => {
      //Code...
    }
    case GetTransactions => broker forward Broker.GetTransactions
    case GetStatus => blockchain forward GetChain
    case GetLastBlockIndex => blockchain forward GetLastIndex
    case GetLastBlockHash => blockchain forward GetLastHash
  }

This is an overview of the usual receive function that we should override. I will analyze the logic of the most complex cases later, now let’s look at the last four. Here we forward the messages to the Blockchain Actor, since it isn’t required any processing. Using the forward operator the sender() of the message will be the one that originated the message, not the Node Actor. In this way the Blockchain Actor will respond to the original sender of the message (the REST API layer).

这是我们应该重写的常规receive函数的概述。 稍后,我将分析最复杂case的逻辑,现在让我们看一下最后四个。 在这里,我们将消息转发到Blockchain Actor,因为它不需要任何处理。 使用forward运算符,消息的sender()将是消息的始发者,而不是Node Actor。 这样,Blockchain Actor将响应消息的原始发送者(REST API层)。

override def receive: Receive = {
    case AddTransaction(transaction) => {
      val node = sender()
      broker ! Broker.AddTransaction(transaction)
      (blockchain ? GetLastIndex).mapTo[Int] onComplete {
        case Success(index) => node ! (index + 1)
        case Failure(e) => node ! akka.actor.Status.Failure(e)
      }
    }
  
  //Code...
}

The AddTransaction message triggers the logic to store a new transaction in the list of pending ones of our blockchain. The Node Actor responds with the index of the block that will contain the transaction.

AddTransaction消息触发了将新交易存储在我们的区块链未决交易列表中的逻辑。 Node Actor以将包含事务的块的index作为响应。

First of all we store the “address” of the sender() of the message in a node value to use it later. We send to the Broker Actor a message to add a new transaction, then we ask to the Blockchain Actor the last index of the chain. The ask operator — the one expressed with ? — is used to send a message to an actor and wait for a response. The response (mapped to an Int value) can be a Success or a Failure.

首先,我们将消息的sender()的“地址”存储在node值中,以备后用。 我们向经纪人Actor发送一条消息以添加新交易,然后我们ask区块链Actor ask链的最后一个索引。 ask运算符-用表示的那个? —用于向演员发送消息并等待响应。 响应(映射到Int值)可以是SuccessFailure

In the first case we send back to the sender (node) the index+1, since it will be the index of the next mined block. In case of failure, we respond to the sender with a Failure containing the reason of the failure. Remember this pattern:

在第一种情况下,我们将index+1发送回发送方( node ),因为它将是下一个已开采区块的索引。 如果发生故障,我们将以包含Failure原因的“故障”响应发件人。 记住这种模式:

ask → wait for a response → handle success/failure

询问→等待回应→处理成功/失败

because we will see it again.

因为我们会再次看到它。

override def receive: Receive = {
    //Code...
  
    case CheckPowSolution(solution) => {
      val node = sender()
      (blockchain ? GetLastHash).mapTo[String] onComplete {
        case Success(hash: String) => miner.tell(Validate(hash, solution), node)
        case Failure(e) => node ! akka.actor.Status.Failure(e)
      }
    }
  
  //Code...
}
view raw

This time we have to check if a solution to the PoW algorithm is correct. We ask to the Blockchain Actor the hash of the last block, and we tell the Miner Actor to validate the solution against the hash. In the tell function we pass to the Miner the Validate message along with the address of the sender, so that the miner can respond directly to it. This is another approach, like the forward one we saw before.

这次我们必须检查PoW算法的解决方案是否正确。 我们向区块链参与者询问最后一个区块的哈希值,然后告诉矿工参与者针对哈希值验证解决方案。 在tell函数中,我们将Validate消息以及发送者的地址传递给矿工,以便矿工可以直接对其进行响应。 这是另一种方法,就像forward一个我们以前看到。

override def receive: Receive = {
    //Code...
  
    case AddBlock(proof) => {
      val node = sender()
      (self ? CheckPowSolution(proof)) onComplete {
        case Success(_) => {
          (broker ? Broker.GetTransactions).mapTo[List[Transaction]] onComplete {
            case Success(transactions) => blockchain.tell(AddBlockCommand(transactions, proof), node)
            case Failure(e) => node ! akka.actor.Status.Failure(e)
          }
          broker ! Clear
        }
        case Failure(e) => node ! akka.actor.Status.Failure(e)
      }
    }
  
    //Code...
}

Other nodes can mine blocks, so we may receive a request to add a block that we didn’t mine. The proof is enough to add the new block, since we assume that all the nodes share the same list of pending transactions.

其他节点可以挖掘块,因此我们可能会收到添加未挖掘块的请求。 该证明足以添加新的块,因为我们假设所有节点共享相同的待处理事务列表。

override def receive: Receive = {
    //Code...
  
    case Mine => {
      val node = sender()
      (blockchain ? GetLastHash).mapTo[String] onComplete {
        case Success(hash) => (miner ? Miner.Mine(hash)).mapTo[Future[Long]] onComplete {
          case Success(solution) => waitForSolution(solution)
          case Failure(e) => log.error(s"Error finding PoW solution: ${e.getMessage}")
        }
        case Failure(e) => node ! akka.actor.Status.Failure(e)
      }
    }
  
    //Code...
  }

  def waitForSolution(solution: Future[Long]) = Future {
    solution onComplete {
      case Success(proof) => {
        broker ! Broker.AddTransaction(createCoinbaseTransaction(nodeId))
        self ! AddBlock(proof)
        miner ! Ready
      }
      case Failure(e) => log.error(s"Error finding PoW solution: ${e.getMessage}")
    }
  }

This is a simplification, in the Bitcoin network there cannot be such assumption. First of all we should check if the solution is valid. We do this sending a message to the node itself: self ? CheckPowSolution(proof). If the proof is valid, we get the list of pending transaction from the Broker Actor, then we tell to the Blockchain Actor to add to the chain a new block containing the transactions and the validated proof. The last thing to do is to command the Broker Actor to clear the list of pending transactions.

这是一种简化,在比特币网络中不可能有这样的假设。 首先,我们应该检查解决方案是否有效。 我们这样做是向节点本身发送一条消息: self ? CheckPowSolution(proof) self ? CheckPowSolution(proof) 。 如果证明有效,我们从经纪人代理那里获得未决交易的清单,然后tell区块链参与者将包含交易和经过验证的证明的新区块添加到链中。 最后要做的是命令Broker Actor清除挂起的事务列表。

The last message is the request to start mining a new block. We need the hash of the last block in the chain, so we request it to the Blockchain Actor. Once we have the hash, we can start mining a new block.

最后一条消息是开始挖掘新块的请求。 我们需要链中最后一个区块的哈希,因此我们将其请求给Blockchain Actor。 有了哈希后,就可以开始挖掘新块了。

The PoW algorithm is a long-running operation, so the Miner Actor responds immediately with a Future containing the computation. The waitForSolution function waits for the computation to complete, while the Node Actor keeps doing its business.

PoW算法是一项长期运行的操作,因此,矿工Actor立即使用包含计算的Future做出响应。 waitForSolution函数等待计算完成,而Node Actor继续进行其业务。

When we have a solution, we reward ourselves adding the coinbase transaction to the list of pending transactions. Then we add the new block to the chain and tell the Miner Actor to be ready to mine another block.

当我们有解决方案时,我们会奖励自己将coinbase交易添加到未决交易列表中。 然后,我们将新块添加到链中,并告知矿工演员准备开采另一个块。

带有Akka HTTP的REST API (REST API with Akka HTTP)

This last section describes the server and REST API. This is the most “external” part of our application, the one connecting the outside world to the Scalachain node. We will make use of Akka HTTP library, which is part of the Akka Framework. Let’s start looking at the server, the entry point of our application.

最后一部分介绍了服务器和REST API。 这是我们应用程序中最“外部”的部分,将外部世界连接到Scalachain节点。 我们将使用Akka HTTP库,它是Akka Framework的一部分。 让我们开始看看服务器,这是我们应用程序的入口点。

object Server extends App with NodeRoutes {

  val address = if (args.length > 0) args(0) else "localhost"
  val port = if (args.length > 1) args(1).toInt else 8080

  implicit val system: ActorSystem = ActorSystem("scalachain")

  implicit val materializer: ActorMaterializer = ActorMaterializer()

  val node: ActorRef = system.actorOf(Node.props("scalaChainNode0"))

  lazy val routes: Route = statusRoutes ~ transactionRoutes ~ mineRoutes

  Http().bindAndHandle(routes, address, port)

  println(s"Server online at http://$address:$port/")

  Await.result(system.whenTerminated, Duration.Inf)

}

Since the Server is our entry point, it needs to extend the App trait. It extends also NodeRoutes, a trait that contains all the http routes to the various endpoint of the node.

由于Server是我们的切入点,因此它需要扩展App特性。 它还扩展了NodeRoutes ,这是一个特征,其中包含到节点各个端点的所有http路由。

The system value is where we store our ActorSystem. Every actor created in this system will be able to talk to the others inside it. Akka HTTP requires also the definition of another value, the ActorMaterializer. This relates to the Akka Streams module, but since Akka HTTP is built on top of it, we still need this object to be initialized in our server (if you want to go deep on the relation with streams, look here).

system值是我们存储ActorSystem 。 在此系统中创建的每个演员都可以与其中的其他角色交谈。 Akka HTTP还需要定义另一个值ActorMaterializer 。 这与Akka Streams模块有关,但是由于Akka HTTP是在其之上构建的,因此我们仍然需要在服务器中初始化该对象(如果您想深入了解与流的关系,请参见此处 )。

The Node Actor is created along with the HTTP routes of the node, that are chained using the ~ operator. Don’t worry about the routes now, we will be back to them in a moment.

将使用~运算符将Node Actor与节点的HTTP路由一起创建。 现在不用担心路线,我们稍后会再与他们联系。

The last thing to do is to start our server using the function Http().bindHandle, that will also bind the routes we pass to it as an argument. The Await.result function will wait the termination signal to stop the server.

最后要做的是使用功能Http().bindHandle启动服务器,该服务器还将绑定我们作为参数传递给它的路由。 Await.result函数将等待终止信号以停止服务器。

The server will be useless without the routes to trigger the business logic of the application. We define the routes in the trait NodeRoutes, differentiating them according to the different logic they trigger:

如果没有路由来触发应用程序的业务逻辑,服务器将毫无用处。 我们在特征NodeRoutes定义路由,并根据它们触发的不同逻辑对其进行区分:

  • statusRoutes contains the endpoints to ask the Scalachain node for its status.

    statusRoutes包含向Scalachain节点询问其状态的端点。

  • transactionRoutes handles everything related to transactions.

    transactionRoutes处理与事务相关的所有事情。

  • mineRoutes has the endpoint to start the mining process

    mineRoutes具有端点来开始挖掘过程

Notice that this differentiation is a logic one, just to keep things ordered and readable. The three routes will be chained in a single one after their initialization in the server.

请注意,这种区分是一种逻辑,只是为了保持事物的有序性和可读性。 在服务器中初始化后,这三个路由将被链接为一个路由。

//Imports...
import com.elleflorio.scalachain.utils.JsonSupport._
// Imports...

trait NodeRoutes extends SprayJsonSupport {

  implicit def system: ActorSystem

  def node: ActorRef

  implicit lazy val timeout = Timeout(5.seconds)

  //Code...
}

The NodeRoutes trait extends SprayJsonSupport to add JSON serialization/deserialization. SprayJson is a Scala library analogous to Jackson in Java, and it comes for free with Akka HTTP.

NodeRoutes特性扩展了SprayJsonSupport以添加JSON序列化/反序列化。 SprayJson是一个Scala库,类似于Java中的Jackson,它随Akka HTTP免费提供。

To convert our objects to a JSON string we import the class JsonSupport defined in the utils package, which contains custom reader/writer for every object. I won’t go into the details, you can find the class in the repository if you want to look at the implementation.

要将对象转换为JSON字符串,我们导入utils包中定义的JsonSupport类, JsonSupport包含每个对象的自定义读取器/写入器。 我不会详细介绍,如果您要查看实现,则可以在存储库中找到该类

We have a couple of implicit values. The ActorSystem is used to define the system of actors, while the Timeout is used by the OnSuccess function that waits for a response from the actors. The ActorRef is defined by overriding in the server implementation.

我们有几个隐式值。 ActorSystem用于定义参与者的系统,而TimeoutOnSuccess函数使用,该函数等待参与者的响应。 通过覆盖服务器实现中定义ActorRef

//Code...

lazy val statusRoutes: Route = pathPrefix("status") {
    concat(
      pathEnd {
        concat(
          get {
            val statusFuture: Future[Chain] = (node ? GetStatus).mapTo[Chain]
            onSuccess(statusFuture) { status =>
              complete(StatusCodes.OK, status)
            }
          }
        )
      }
    )
  }

//Code...

The endpoint to get the status of the blockchain is defined in the statusRoutes. We define the pathPrefix as "status" so the node will respond to the path ` http://<address>:<port/status. After that there is the definition of the HTTP actions we want to enable on the path. Here we want to get the status of the blockchain, so we define only the get action. Inside that we ask the Node Actor to get the current Chain. When the actor responds, the Chain is sent as a JSON along with an ok status in the complete method.

用于获取区块链状态的端点在statusRoutes中定义。 我们将pathPrefix定义为“状态”,以便节点将响应路径`http:// <地址>:<端口/状态。 然后,定义了我们要在路径上启用的HTTP操作。 在这里,我们要获取区块链的状态,因此我们仅定义get操作。 在其中,我们要求Node Actor获取当前的Chain。 当actor响应时,在complete方法中,Chain作为JSON连同ok状态一起发送。

//Code...

lazy val transactionRoutes: Route = pathPrefix("transactions") {
    concat(
      pathEnd {
        concat(
          get {
            val transactionsRetrieved: Future[List[Transaction]] =
              (node ? GetTransactions).mapTo[List[Transaction]]
            onSuccess(transactionsRetrieved) { transactions =>
              complete(transactions.toList)
            }
          },
          post {
            entity(as[Transaction]) { transaction =>
              val transactionCreated: Future[Int] =
                (node ? AddTransaction(transaction)).mapTo[Int]
              onSuccess(transactionCreated) { done =>
                complete((StatusCodes.Created, done.toString))
              }
            }
          }
        )
      }
    )
  }

//Code...

The transactionRoutes allows the interaction with the pending transactions of the node. We define the HTTP action get to retrieve the list of pending transactions. This time we also define the HTTP action post to add a new transaction to the list of pending ones. The entity(as[Transaction]) function is used to deserialize the JSON body into a Transaction object.

transactionRoutes允许与节点的待处理事务进行交互。 我们定义HTTP操作get来检索未决事务列表。 这次,我们还定义了HTTP操作post以将新事务添加到挂起的事务列表中。 entity(as[Transaction])函数用于将JSON主体反序列化为Transaction对象。

//Code...
lazy val mineRoutes: Route = pathPrefix("mine") {
    concat(
      pathEnd {
        concat(
          get {
            node ! Mine
            complete(StatusCodes.OK)
          }
        )
      }
    )
  }

//Code...

The last route is the MineRoutes. This is a very simple one, used only to ask the Scalachain node to start mine a new block. We define a get action since we do not need to send anything to start the mining process. It is not required to wait for a response, since it may take some time, so we immediately respond with an Ok status.

最后一条路线是MineRoutes 。 这是一个非常简单的示例,仅用于要求Scalachain节点开始挖掘一个新块。 我们定义了一个get动作,因为我们不需要发送任何东西就可以开始挖掘过程。 不需要等待响应,因为它可能需要一些时间,因此我们会立即以Ok状态进行响应。

The API to interact with the Scalachain node are documented here.

与Scalachain节点进行交互的API记录在这里

结论 (Conclusion)

With the last section, we concluded our tour inside Scalachain. This prototype of a blockchain is far from a real implementation, but we learned a lot of interesting things:

在最后一部分中,我们结束了Scalachain内部之旅。 区块链的原型远非真正的实现,但我们学到了很多有趣的东西:

  • How a blockchain works, at least from an high level perspective.

    至少从高层的角度来看,区块链是如何工作的。
  • How to use functional programming (Scala) to build a blockchain.

    如何使用函数式编程(Scala)构建区块链
  • How the Actor Model works, and its application to our use case using the Akka Framework.

    Actor模型如何工作,以及如何使用Akka Framework将其应用到我们的用例中。
  • How to use the Akka HTTP library to create a sever to run our blockchain, along with the APIs to interact with it.

    如何使用Akka HTTP库创建一个服务器来运行我们的区块链,以及与之交互的API。

The code is not perfect, and some things can be implemented in a better way. For this reason, feel free to contribute to the project! ;-)

代码并不完美,有些事情可以用更好的方式实现。 因此, 随时为该项目做贡献! ;-)

翻译自: https://www.freecodecamp.org/news/how-to-build-a-simple-actor-based-blockchain-aac1e996c177/

vtk删除一个actor

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cumi7754/article/details/108108575

智能推荐

OpenLayers 5 使用GeoJSON数据渲染热力图_战斗中的老胡的博客-程序员ITS304_geojson 热力图

GIS开发中会遇到需要使用热力图Heatmap的时候,openlayers5官方示例给出的是kml文件描述的热力图数据,开发中接触更多的还是GeoJSON格式,本文就使用GeoJSON格式来实现一个热力图。一、实现思路https://openlayers.org/en/latest/examples/heatmap-earthquakes.html官方例子是从一个kml文件解析出生成的热力...

findfirst_当心findFirst()和findAny()_diluan6799的博客-程序员ITS304

那么findFirst()和findAny()有什么问题呢? 从我们的Javadoc( 此处和此处 )可以看出,这两个方法都从流中返回任意元素-除非流具有遇到顺序 ,在这种情况下, findFirst()返回第一个元素。 简单。 一个简单的示例如下所示: public Optional&lt;Customer&gt; findCustomer(String customerId) { ...

idea建立webservice服务端和客户端程序_wfpc__的博客-程序员ITS304_idea webservice

@WebService获取数据库数据idea建立webservice服务端和客户端程序,并把服务端程序放在tomcat首先要建立服务端程序,再建立客户端程序,再把服务端程序放在tomcat下,最后客户端能够获取到tomcat上webservice的数据就可以了。本文是通过建立webservice服务端程序连接另一服务器上的odbc数据源,通过客户端程序传入sql语句获取到返回的odbc数据。...

38.JAVA入门__Switch语句(春夏秋冬)_天天DEBUG的博客-程序员ITS304

Switch语句(春夏秋冬)//case穿透//case穿透//case穿透case后面没有break将会穿透,直到遇到break或者整体switch结束。package class1;// 12个月 输入一个月,判断春夏秋冬import java.util.Scanner;public class thirty_eight {public static void main(String[] args) { // TODO Auto-generated method stub Scan

全连接网络原理_Iseno_V的博客-程序员ITS304_全连接网络原理

全连接网络原理上一期介绍了只包含单隐层的浅层全连接网络,本期介绍更具有普遍性的深层全连接网络。推荐先看一下上期的内容,将更有助于理解。上一期的链接为:https://blog.csdn.net/Iseno_V/article/details/102941210公式推导部分依旧采用截图的形式,如果需要源文档可以给我留言。1. 网络结构图下图为一个2分类问题的四层结构全连接网络。2. 原...

处理垃圾短信的方法(10086999)_五角大寨的博客-程序员ITS304

最近老收到垃圾短信,烦死了,一急找到一个好的解决办法。  首先编辑你收到的垃圾短信,就是在原短信前加上发送号码,加*号,转发给10086999,很快,你就收到电信的回复: 尊敬的客户:您向我公司转发的不良信息已收到,感谢您对中国移动的支持和关心,我公司将会根据您提供的信息联合社会各界进行查证和处理,谢谢!  要注意的就是不能转发彩信,如果垃圾短信是用彩信发的就把彩信

随便推点

华为 基于策略划分VLAN的配置方法及示例_茶乡浪子的博客-程序员ITS304_policy vlan

学过思科交换机的朋友,可能对基于策略划分VLAN的配置方法印象非常深,感觉确实比较复杂,先要配置VMPS以及VMPS数据库,但在华为交换机中,这种现象得到了彻底改变,因为它有了一种特殊的端口类型——Hybrid。说它特殊是因为Hybrid端口既可以像Access类型端口那样在发送数据时不带VLAN标签,又可以像Trunk类型端口那样在发送数据时带上VLAN标签,且同时允许多个VLAN的帧通过。这就为华为在许多方面的配置优化打下了基础,此处介绍的基于策略划分VLAN就是其中一个。通过下面的学习,你一定会明显感

单片机的学习经验_嵌入式-鱼的博客-程序员ITS304_单片机经验

单片机是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计时器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域的广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的32位300M的高速单

人脸属性分析--性别、年龄和表情识别_迷若烟雨的博客-程序员ITS304_人脸属性识别

人脸属性指的是根据给定的人脸判断其性别、年龄和表情等,当前在github上开源了一些相关的工作,大部分都是基于tensorflow的,还有一部分是keras,CVPR2015曾有一篇是用caffe做的.CSDN从0到1实现基于Tornado和Tensorflow的人脸、年龄、性别识别基于caffe的表情识别tensorflow练习12:利用图片预测年龄与性别怎样用Keras识别...

模拟aloha协议_Addmana的博客-程序员ITS304

clearG = 5:0.01:0.1;n=length(G);t0=0.5;i=0;u=0;b=0;tb = 0;tu=0;ti=0;for j = 1:length(G)r = exprnd(G(j),1,n);if r(j)<t0 b=b+1; tb = r(j)+t0;elseif r(j)>t0 u=u+1; ti =ti+(r(j

和机器学习和计算机视觉相关的数学_FrankJingle的博客-程序员ITS304

1. 线性代数 (Linear Algebra): 我想国内的大学生都会学过这门课程,但是,未必每一位老师都能贯彻它的精要。这门学科对于Learning是必备的基础,对它的透彻掌握是必不可少的。我在科大一年级的时候就学习了这门课,后来到了香港后,又重新把线性代数读了一遍,所读的是Introduction to Linear Algebra (3rd Ed.)  by Gilbert Str

ipad air4和ipad pro2020的区别 哪个性价比高_sdsadwe的博客-程序员ITS304

iPad Air4 和iPad Pro2020 都是采用全面屏设计,iPad Air4 尺寸为10. 9 英寸、iPad Pro2020 为 11 英寸和12. 9 英寸 平板选ipad air4还是ipad pro2020这些点很重要看过你就懂了http://www.adiannao.cn/22、解锁方式iPad Air4 为电源指纹解锁,iPad Pro2020 为Face ID面容解锁,使用体验上,iPad Pro2020 属于更高端的方式3、性能上,iPad Air4 处理器为A14 芯片,5

推荐文章

热门文章

相关标签