PHP从零实现区块链(网页版四)交易1

发布时间:2024年01月20日

源码地址:PHP从零实现区块链(四)交易1 - 简书

注:本例只是从网页版实现一下原理,源码非本人所写,只是将原帖的源码更改了一下,变成网页版

开始这个例子前,先解释一些概念以及统一命名叫法,这样便于理解代码。

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。

另附:在后面章节中将会实现帐户功能,因为这里主要是实现交易功能,像这里的账号,你只要知道他的名字,可以随便转账,这显然是不行的。

文章来源:https://blog.csdn.net/d3582077/article/details/135661441
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。