注:本例只是从网页版实现一下原理,源码非本人所写,只是将原帖的源码更改了一下,变成网页版
开始这个例子前,先解释一些概念以及统一命名叫法,这样便于理解代码。
1.这里的交易是采用UXTO模式。这也是比特币中采用的模式。
就是只记录帐号的交易事件,类似于某个点发送出去多少币,然后收到多少币。
为了便于理解,我先这样说,实际代码实现是有差别的。
那么它没有账户的具体余额,记录的只有一笔笔的交易,那么怎么得出余额呢?
得遍历区块的所有交易事件,找出这个账户总共接收了多少币减去总共发送的币的,就得出你的余额了。
你可以先这样简单的理解UXTO模式(实际“发送”和“接收”是采用共同的output记录)
2.Tranaction类
在下面的例子中,我们将看到tranaction数组将存在区块block里,代替了原本的data数据。
这个tranaction中就存储有一笔笔的转账记录,你可以叫它tx,tx1,tx2之类的,在后面指的就是它。
我在这里就把它叫做一个交易区块。
3.Txinput和TxOutput
而在一个Tranaction下,又包含有一组Txinput(数组形式)和一组TxOutput(数组形式)。
每个txoutput表示一笔输出交易,表示输出到这个地址多少币。(一笔输出记录)
而每个Txinput表示一笔输入记录,是这个币是从哪个地址输入的。也就是将会扣除此地址对应的币数量。
好,那上面的层次关系理解了,下面给出实际代码,我们的重点是主要搞清楚Txinput和Txoutput,而tranaction和block只是对它们一层一层的封装。
新建一个Transaction.php文件,代码如下:
<?php
namespace App\Services;
class Transaction
{
// coinbase 交易的奖励
const subsidy = 50;
/**
* 当前交易的Hash
* @var string $id
*/
public $id;
/**
* @var TXInput[] $txInputs
*/
public $txInputs;
/**
* @var TXOutput[] $txOutputs
*/
public $txOutputs;
public function __construct(array $txInputs, array $txOutputs)
{
$this->txInputs = $txInputs;
$this->txOutputs = $txOutputs;
$this->setId();
}
private function setId()
{
$this->id = hash('sha256', serialize($this));
}
}
这个类的构造函数,将txInputs和txOutputs数组传进去,赋给类自有的txInputs和txoutputs。
然后就调用它的setId方法,将类序列化后,进行哈希运算,得到的哈希值就作为这个类的
id。const subsidy = 50;是一个常量,没什么特别的含义。在后面发起第一笔交易会用到。
这样一个交易区块就产生了(tx)。
我们记住,它的id就是这个区块的你可以看作是标识符,通过哈希运算得到。
而txInputs就记录着一笔笔输入记录。
txoutputs记录着一笔笔输出记录。
注意这里的输入输出针对的主体是交易区块,比如输出给张三50个币,那么这个币得有个来源,input就指明了来源,那么可以知道,一个交易区块内,两边的币数是相等的。
好,下面来看TxInput类和TxOutput类的具体实现
新建TxInput.php文件,代码如下:
<?php
namespace App\Services;
class TXInput
{
/**
* @var string $txId
*/
public $txId;
/**
* @var int $vOut
*/
public $vOut;
/**
* @var string $scriptSig
*/
public $scriptSig;
public function __construct(string $txId, int $vOut, string $scriptSig)
{
$this->txId = $txId;
$this->vOut = $vOut;
$this->scriptSig = $scriptSig;
}
public function canUnlockOutputWith(string $unlockingData): bool
{
return $this->scriptSig == $unlockingData;
}
}
新建?TxOutput.php,代码如下:
<?php
namespace App\Services;
class TXOutput
{
/**
* @var int $value
*/
public $value;
/**
* @var string $scriptPubKey
*/
public $scriptPubKey;
public function __construct(int $value, string $scriptPubKey)
{
$this->value = $value;
$this->scriptPubKey = $scriptPubKey;
}
public function canBeUnlockedWith(string $unlockingData): bool
{
return $this->scriptPubKey == $unlockingData;
}
}
1.关于TxOutput类很好理解,value值就是币的数量,scriptPubkey这里储存的是账号地址。
意思就是转给这个地址scriptPubkey多少币(value)。
通过构造函数传进去即可。
而这里面的canBeUnlockdWith函数,将在查询时用到,是怎么查询的呢?
比如我要查账号地址,zhangsan收到了多少币。那么遍历所有的区块,得到TxOutput对象。
然后调用canBeUnlockedWith函数,如果地址相等,那么说明这笔output是张三的。
这就是这个函数的作用。
2.关于TxInput类,如果是李四转张三50个币,所以应该是TxInput里写着李四的地址,然后也有个value值,记录着50个币。
注意了,这里不是这样实现的,我们来看看TxInput里的三个变量:
? ? public $txId;
? ? public $vOut;
? ? public $scriptSig;
地址是存在scriptSig里的,这个没什么不同的。
但是并没有value变量,而是txId和vOut,什么意思呢?这两个变量定位到了一笔output。
txId就是那笔output所属交易区块的id,vOut就是那个交易区块下的output数组的索引。
而这笔output是和李四的地址绑定的,然后就会扣除掉李四的这笔output。
在后面我会采用通用的说法,就是只要被input指向过的output,我们称之为这笔output 是花费过的。
那么问题来了。input不能自定义一个值,得引入之前的output,会有以下情况:
如果你要转给张三50个币,
你没有这笔正好是50的output怎么办。
那么就是这样解决的,存在以下几种情况。
如果你有一笔output是多于50,比如70。
那么在input中引用这笔output
然后在output中,output张三50后,再建一笔output 20指向自己的地址,就是找零的意思。
然后如果你的output都是少于50.
就引用多笔output,然后多出的零头之类的,再output自己的地址。
那么关于input和ouput关系就明朗了,每笔input都得引入之前的某笔output,而每笔output则不是必需,如果有对应的,那就是花费过了,如果没有,就是你的余额。关系如下图:
图片来源:Building Blockchain in Go. Part 4: Transactions 1 - Going the distance?
PS:这是国外的go语言版本,就是最原始的版本,你们有兴趣的也可以看看。
清楚了input,里面的代码我就不解释了,都差不多,构造函数,和解锁函数,参考output。
接着我们在Block.php进行一些修改,代码如下:
<?php
namespace App\Services;
class Block
{
//......
/**
* @var Transaction[] $transactions
*/
public $transactions;
public function __construct(array $transactions, string $prevBlockHash)
{
$this->prevBlockHash = $prevBlockHash;
$this->transactions = $transactions;
$this->timestamp = time();
$pow = new ProofOfWork($this);
list($nonce, $hash) = $pow->run();
$this->nonce = $nonce;
$this->hash = $hash;
}
public static function NewGenesisBlock(Transaction $coinbase)
{
return $block = new Block([$coinbase], '');
}
public function hashTransactions(): string
{
$txsHashArr = [];
foreach ($this->transactions as $transaction) {
$txsHashArr[] = $transaction->id;
}
return hash('sha256', implode('', $txsHashArr));
}
}
修改的地方有四点:
第一点:增加tranactions变量??public $transactions;
第二点: 将里面的构造函数?$this->data = $data;这句
改为存储tranaction数组,就是现在要存储具体的交易数据了。
?$this->transactions = $transactions;
然后自然,构造函数,第一个参数传入也得是tranaction数组。
?public function __construct(array $transactions, string $prevBlockHash)
第三点: 创世块,自然也不能传自定义的数据了,也得是transacions数组,比较块结构已经改写了。new Block需要的是tranactions。
? ? public static function NewGenesisBlock()
? ? {
? ? ? ? return $block = new Block('Genesis Block', '');
? ? }
改为:
? ?public static function NewGenesisBlock(Transaction $coinbase)
? ? {
? ? ? ? return $block = new Block([$coinbase], '');
? ? }
第四点:增加hashTransactions函数
因为Block增加了一个tranactions数组,它现在不是取得tranactions里面一个个变量的数据,然后拼起来哈希运算。
而是通过hashTransactions函数,获取tranactions数组下每个tranaction的ID,然后拼起来,进行哈希运算。接着再把这个总哈希值返回去。(在prepareData里调用)
而tranaction下的ID,之前讲过了,是序列化后哈希值。
那么经过hasTransactions这样一翻操作后,相当于变向的哈希了整个tranasctions数组。
然后相应的,在ProofOfWork中更改如下:
public function prepareData(int $nonce): string
{
return implode('', [
$this->block->prevBlockHash,
$this->block->hashTransactions(),
$this->block->timestamp,
config('blockchain.targetBits'),
$nonce
]);
}
这里的$this->block->hashTransactions(),代替了之前的$this->block->data,
它也不是$this->block->transactions,而是通过hashTransactions方法,得到的transactions哈希值。是一样的效果。
因为我们在创建创世块的时候,需要new Block([$coinbase], '');,第一个参数是个transactions数组。
所以我们需要在Transaction类中,创建下面的函数,用于创建创世块所需要transactions。
代码如下:
public static function NewCoinbaseTX(string $to, string $data): Transaction
{
if ($data == '') {
$data = sprintf("Reward to '%s'", $to);
}
$txIn = new TXInput('', -1, $data);
$txOut = new TXOutput(self::subsidy, $to);
return new Transaction([$txIn], [$txOut]);
}
注意这里的txInput,没有输入记录,只是标明这是创世块或coinbase,相当于挖矿得到币的,系统产生的。所以也没有对应的地址和指向哪个output。
只有一个TxOutput表明,输出了多少币给$to,因为它的来源不是别人转的。
然后再建立一个正常的交易方法,就是某人给某人转帐用的:
class Transaction
{
public static function NewUTXOTransaction(string $from, string $to, int $amount, BlockChain $bc): Transaction
{
list($acc, $validOutputs) = $bc->findSpendableOutputs($from, $amount);
if ($acc < $amount) {
echo "余额不足";
exit;
}
$inputs = [];
$outputs = [];
/**
* @var TXOutput $output
*/
foreach ($validOutputs as $txId => $outsIdx) {
foreach ($outsIdx as $outIdx) {
$inputs[] = new TXInput($txId, $outIdx, $from);
}
}
$outputs[] = new TXOutput($amount, $to);
if ($acc > $amount) {
$outputs[] = new TXOutput($acc - $amount, $from);
}
return new Transaction($inputs, $outputs);
}
public function isCoinbase(): bool
{
return (count($this->txInputs) == 1) && ($this->txInputs[0]->txId == '') && ($this->txInputs[0]->vOut == -1);
}
}
上面的代码,想要理解,核心点在于理解findSpendableOutputs函数,这个函数添加在BlockChain类里,代码如下:
class BlockChain implements \Iterator
{
/**
* 找出地址的所有未花费交易
* @param string $address
* @return Transaction[]
*/
public function findUnspentTransactions(string $address): array
{
$unspentTXs = [];
$spentTXOs = [];
/**
* @var Block $block
*/
foreach ($this as $block) {
foreach ($block->transactions as $tx) {
$txId = $tx->id;
foreach ($tx->txOutputs as $outIdx => $txOutput) {
if (isset($spentTXOs[$txId])) {
foreach ($spentTXOs[$txId] as $spentOutIdx) {
if ($spentOutIdx == $outIdx) {
continue 2;
}
}
}
if ($txOutput->canBeUnlockedWith($address)) {
$unspentTXs[$txId] = $tx;
}
}
if (!$tx->isCoinbase()) {
foreach ($tx->txInputs as $txInput) {
if ($txInput->canUnlockOutputWith($address)) {
$spentTXOs[$txInput->txId][] = $txInput->vOut;
}
}
}
}
}
return $unspentTXs;
}
/**
* 找出所有已花费的输出
* @param string $address
* @return array
*/
public function findSpentOutputs(string $address): array
{
$spentTXOs = [];
/**
* @var Block $block
*/
foreach ($this as $block) {
foreach ($block->transactions as $tx) {
if (!$tx->isCoinbase()) {
foreach ($tx->txInputs as $txInput) {
if ($txInput->canUnlockOutputWith($address)) {
$spentTXOs[$txInput->txId][] = $txInput->vOut;
}
}
}
}
}
return $spentTXOs;
}
// 根据所有未花费的交易和已花费的输出,找出满足金额的未花费输出,用于构建交易
public function findSpendableOutputs(string $address, int $amount): array
{
$unspentOutputs = [];
$unspentTXs = $this->findUnspentTransactions($address);
$spentTXOs = $this->findSpentOutputs($address);
$accumulated = 0;
/**
* @var Transaction $tx
*/
foreach ($unspentTXs as $tx) {
$txId = $tx->id;
foreach ($tx->txOutputs as $outIdx => $txOutput) {
if (isset($spentTXOs[$txId])) {
foreach ($spentTXOs[$txId] as $spentOutIdx) {
if ($spentOutIdx == $outIdx) {
// 说明这个tx的这个outIdx被花费过
continue 2;
}
}
}
if ($txOutput->canBeUnlockedWith($address) && $accumulated < $amount) {
$accumulated += $txOutput->value;
$unspentOutputs[$txId][] = $outIdx;
if ($accumulated >= $amount) {
break 2;
}
}
}
}
return [$accumulated, $unspentOutputs];
}
/**
* 找出所有未花费的输出
* @param string $address
* @return TXOutput[]
*/
public function findUTXO(string $address): array
{
$UTXOs = [];
$unspentTXs = $this->findUnspentTransactions($address);
$spentTXOs = $this->findSpentOutputs($address);
foreach ($unspentTXs as $transaction) {
$txId = $transaction->id;
foreach ($transaction->txOutputs as $outIdx => $output) {
if (isset($spentTXOs[$txId])) {
foreach ($spentTXOs[$txId] as $spentOutIdx) {
if ($spentOutIdx == $outIdx) {
// 说明这个tx的这个outIdx被花费过
continue 2;
}
}
}
if ($output->canBeUnlockedWith($address)) {
$UTXOs[] = $output;
}
}
}
return $UTXOs;
}
}
findSpendableOutputs解释:
在这个函数里开头就调用了这两句:
? ? $unspentTXs = $this->findUnspentTransactions($address);
? ? $spentTXOs = $this->findSpentOutputs($address);
所以我们先来理解findUnspentTransactions
这个函数是寻找对应地址未被花费过的output(没被input引用过的output)
交易区块中只要有一个output满足条件,不管此区块是否还有其它被input过的output。
就将此交易区块添加到unspentTXs数组里 $unspentTXs[$txId] = $tx
并且以此交易区块的id名作为元素下标。
代码有点多,我就讲一下大概的逻辑流程了,它的寻找方法是这样。
首先遍历所有区块下的output:
foreach ($this as $block) {
? ? ? ? ? ? foreach ($block->transactions as $tx) {
? ? ? ? ? ? ? ? $txId = $tx->id;
? ? ? ? ? ? ? ? foreach ($tx->txOutputs as $outIdx => $txOutput)
然后判断此output是否被花费过(input过的output),如果是被花费过,则跳过此output,不进行添加。
if (isset($spentTXOs[$txId])) {
? ? ? ? ? ? ? ? ? ? ? ? foreach ($spentTXOs[$txId] as $spentOutIdx) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? if ($spentOutIdx == $outIdx) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? continue 2;
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
上面的判断原理是这样的,先是判断这个交易区块里有没有存在被这个地址input过的output
通过是否设置了spentTXOS[$txID]。这个spentTXOS存储有这个地址的input,并且行数是以交易区块ID作为元素下标名的,是个二维数组,列存着索引。
spenttxos具体实现将在后面解释。
如果存在的话,就找出是哪个output,就是每循环一个output就和spentTXOS[txID]下所有的索引对比一下,如果相等,则表明这个output就是被花费过的,则跳到第二层循环,继续下一个output
不执行下面这个语句:
? ?if ($txOutput->canBeUnlockedWith($address)) {//如果这笔txoutput对应着这个$address
$unspentTXs[$txId] = $tx; //则把这个交易区块赋给unspenttXs数组,并且元素下标名是这个交易区块的id?}
所以说能被添加进的交易区块里面必定有未被花费过的output
然后寻找此区块对应地址的input,将它们添加到一个数组里
if (!$tx->isCoinbase()) {
? ? ? ? ? ? ? ? ? ? foreach ($tx->txInputs as $txInput) {
? ? ? ? ? ? ? ? ? ? ? ? if ($txInput->canUnlockOutputWith($address)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? $spentTXOs[$txInput->txId][] = $txInput->vOut;
? ? ? ? ? ? ? ? ? ? ? ? }
上面意思就是将这个区块下对应的这个地址的input添加到$spentTXOs数组里(如果有)
但是这里有个问题。为何先判断if (isset($spentTXOs[$txId]))
然后再? $spentTXOs[$txInput->txId][] = $txInput->vOut;
那是因为input的特性,只能指向的是之前的区块。
假设倒数第一个区块中有你对应地址的input
而倒数第二个区块中正好也有你对应地址的output。
那么,如果倒数第一个区块中的input没有引用你倒数第二个区块中的output.
那么此output就必定是未花费的。
而不用对比之前所有的input
因为区块按顺序的特性,input不可能知道后面的output也不会指向后面的output.
那么以此类推,如果倒数第三个区块中的output没有被倒数第一第二的input引用。
那么此output也必定是未花费的。
这个就是是边找边对比。(效率高一点)
后面的函数,是先找出所有的input然后再对比。(都可以,这样容易理解一些)
接着是? findSpentOutputs($address)函数(找出已花费的输出)
这个是找出地址所有的input,并将其这样存储:
?$spentTXOs[$txInput->txId][] = $txInput->vOut;
这里的代码跟findUnspentTransactions函数最后那部分的代码差不多。
基本上就是把那部分拿出来单独做一个函数。
理解了这两个函数后,那么下面这两句:
? ?$unspentTXs = $this->findUnspentTransactions($address);
? ?$spentTXOs = $this->findSpentOutputs($address);
就明白了,unspentTXs获得了未花费的transactions(交易区块).
spentTXOs,获得了已花费的output(input)
我直接贴后面的代码吧,在旁边我已经加了注释:
*/
foreach ($unspentTXs as $tx) { //这个地址对应的output所在的交易区块给tx
$txId = $tx->id; //将这个交易区块的id给txId
foreach ($tx->txOutputs as $outIdx => $txOutput) {//遍历此交易区块的txoutput
if (isset($spentTXOs[$txId])) {//如果这个区块的id等于这个地址input所关联的区块id
foreach ($spentTXOs[$txId] as $spentOutIdx) {//那么将这个input关联区块id下的vout给$spentoutidx
if ($spentOutIdx == $outIdx) {//如果相等,说明此output就是input关联的output
// 说明这个tx的这个outIdx被花费过
continue 2;
}
}
}
//上面遍历txoutput意思是,如果这笔output已经被花费了。则跳到下一个output
//否则执行下面语句
if ($txOutput->canBeUnlockedWith($address) && $accumulated < $amount) {//如果这笔output是这个地址对应的,并且找到的未花
//费还小于amount,不足以支付
$accumulated += $txOutput->value; //将继续相加,将这output的收到的币数量相加
$unspentOutputs[$txId][] = $outIdx; //将这个交易块的txid和未花费的output 索引添加进unspentOutputs数组
if ($accumulated >= $amount) { //如果找到未花费的币的数量,可以用来支付交易了。
break 2; // 则跳出foreach ($unspentTXs as $tx) 循环
}
}
}
}
那么,最终这个函数
? public function findSpendableOutputs(string $address, int $amount): array
就是,根据amount数量,获取address里用于足够支付的output。将其添加到unspentOutputs数组
$unspentOutputs[$txId][] = $outIdx;??
然后返回,对应这句:
list($acc,$validOutputs)=$bc->findSpendableOutputs($from,$amount);
里面的validOutputs接收的就是unspentOutputs。
但还有个参数,acc,接收的是$accumulated,这个是记录这笔Outputs总币数。
然后上面还有一个
public function findUTXO(string $address): array
这个函数,没被用到,在后面是用来查询余额的。
跟 findSpendableOutputs差不多,区别是它寻找所有的未花费的output,而不是像 findSpendableOutputs只找到满足交易数量的output就停止寻找了。
接下来我们还要在BlockChain类里移除addBlock,因为现在添加区块是需要transactions数据了。
我们直接新增一个mineBlock()代替,以及修改NewBlockChain(),代码如下:
/**
* @param array $transactions
* @throws \Exception
*/
public function mineBlock(array $transactions)
{
$lastHash = Cache::get('l');
if (is_null($lastHash)) {
echo "还没有区块链,请先初始化";
exit;
}
$block = new Block($transactions, $lastHash);
$this->tips = $block->hash;
Cache::put('l', $block->hash);
Cache::put($block->hash, serialize($block));
}
// 新建区块链
public static function NewBlockChain(string $address): BlockChain
{
if (Cache::has('l')) {
// 存在区块链
$tips = Cache::get('l');
} else {
$coinbase = Transaction::NewCoinbaseTX($address, self::genesisCoinbaseData);
$genesis = Block::NewGenesisBlock($coinbase);
Cache::put($genesis->hash, serialize($genesis));
Cache::put('l', $genesis->hash);
$tips = $genesis->hash;
}
return new BlockChain($tips);
}
NewBlockChain的功能之前说过,是用来创建创世块的。如果不了解的可以翻我前面那章看一下。
只是这里,新建创世块用transaction代替了data,这笔交易是用NewCoinbaseTx创建的,给了自己50个币。
(注意:我忘了改命名空间了,将所有的namespace App\Services;改为namespace App\Http\Controllers;)
好了,上面这些改造都完了的话,至此我们就可以来调用这些函数了。
开始使用交易功能了,创建创世块给一个地址50个币,然后转账给别人,再查询余额。
我们一个一个来。
我们来更改appcontroler下的app,先简单调用$bc = BlockChain::NewBlockChain($data);创建创世块测试一下,后面我将完善app功能,app函数测试代码如下:
<?php
namespace App\Http\Controllers;
use Cache;
use Illuminate\Http\Request;
class AppController extends Controller
{
public function app(Request $request){
if($request->get('add')!="")
{
//添加区块
$data=$request->get('data');
$time1 = time();
$bc = BlockChain::NewBlockChain($data);
$time2 = time();
$spend = $time2 - $time1;
echo('已添加,花费时间(s):'.$spend);
echo('<br>新添加块的哈希值是:<br>'.$bc->tips);
echo('<hr/>所有区块信息:<br>');
foreach ($bc as $block){
print_r($block);
echo('<hr>');
}
}
else
{
//显示所有区块
echo('所有区块信息:<br>');
foreach ($bc as $block){
echo('<hr>');
print_r($block);
}
}
}
}
注意,因为原帖的代码没有贴完整,这句:$coinbase = Transaction::NewCoinbaseTX($address, self::genesisCoinbaseData);
用下面代替:
$coinbase = Transaction::NewCoinbaseTX($address, 'genesisCoinbaseData');
直接用字符串代替,关于原本指的是什么,你们可以直接去原文章的github上查看,那里有完整的代码。
好了,我们来看一下运行效果:
OK,创建创世块功能正常。
接下来我测试一下获取余额功能,但是没有相关的,我们看原文章命令代码是:
public function handle()
? ? {
? ? ? ? $address = $this->argument('address');
? ? ? ? $bc = BlockChain::GetBlockChain();
? ? ? ? $UTXOs = $bc->findUTXO($address);
? ? ? ? $balance = 0;
? ? ? ? foreach ($UTXOs as $output) {
? ? ? ? ? ? $balance += $output->value;
? ? ? ? }
? ? ? ? $this->info(sprintf("balance of address '%s' is: %s", $address, $balance));
? ? }
我们的代码少了个GetBlockChain函数,直接去github复制过来,添加到我们的BlockChain如下:
public static function GetBlockChain(): BlockChain
{
if (!Cache::has('l')) {
echo "还没有区块链,请先初始化";
exit;
}
return new BlockChain(Cache::get('l'));
}
?测试调用代码:
在AppController增加下面函数:
public static function getBalance($address)
{
$bc = BlockChain::GetBlockChain();
$UTXOs = $bc->findUTXO($address);
$balance = 0;
foreach ($UTXOs as $output) {
$balance += $output->value;
}
echo($address."的余额是:".$balance);
}
然后AppController::getBalance('zhengyong');调用,结果如下:
发送代币的就不测试了,我直接给出AppController相关的最终完整代码。
首先修改command.php html代码如下:
<!doctype html>
<html>
<head></head>
<body>
<form name="form1" method="post" action="/app" target=“_blank”>
创世块地址:
<input type="text" name="data">
<input type="submit" name="add" value="添加">
<br>
转帐地址:<input type="text" name="from">to
<input type="text" name="to">数量:
<input type="text" name="amount">
<input type="submit" name="send" value="发送">
<br>
地址:<input type="text" name="address">
<input type="submit" name="balance" value="查询余额">
<br>
<input type="submit" name="query" value="查询所有区块">
</form>
</body>
</html>
对应AppController代码如下:
<?php
namespace App\Http\Controllers;
use Cache;
use Illuminate\Http\Request;
class AppController extends Controller
{
public function app(Request $request){
if($request->get('add')!="")
{
//添加区块
$data=$request->get('data');
$time1 = time();
$bc = BlockChain::NewBlockChain($data);
$time2 = time();
$spend = $time2 - $time1;
echo('花费时间(s):'.$spend);
echo('<br>创世块的哈希值是:<br>'.$bc->tips);
echo('<hr/>所有区块信息:<br>');
foreach ($bc as $block){
print_r($block);
echo('<hr>');
}
}
else if($request->get('send')!="")
{
$from=$request->get('from');
$to=$request->get('to');
$amount=$request->get('amount');
AppController::send($from,$to,$amount);
}
else if($request->get('balance')!="")
{
$address=$request->get('address');
AppController::getBalance($address);
}
else
{
$bc = BlockChain::GetBlockChain();
//显示所有区块
echo('所有区块信息:<br>');
foreach ($bc as $block){
echo('<hr>');
print_r($block);
}
}
}
public static function send($from,$to,$amount)
{
$bc = BlockChain::GetBlockChain();
$tx = Transaction::NewUTXOTransaction($from, $to, $amount, $bc);
$bc->mineBlock([$tx]);
echo('send success');
echo('<br>');
foreach ($bc as $block) {
echo("$block->hash");
break;
}
}
public static function getBalance($address)
{
$bc = BlockChain::GetBlockChain();
$UTXOs = $bc->findUTXO($address);
$balance = 0;
foreach ($UTXOs as $output) {
$balance += $output->value;
}
echo($address."的余额是:".$balance);
}
}
大概测试了下,功能都正常:
本章完结,OK。
另附:在后面章节中将会实现帐户功能,因为这里主要是实现交易功能,像这里的账号,你只要知道他的名字,可以随便转账,这显然是不行的。