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

这篇具有很好参考价值的文章主要介绍了PHP从零实现区块链(网页版四)交易1。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

源码地址: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则不是必需,如果有对应的,那就是花费过了,如果没有,就是你的余额。关系如下图:

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

图片来源: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上查看,那里有完整的代码。

好了,我们来看一下运行效果:

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

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');调用,结果如下:

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

发送代币的就不测试了,我直接给出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);
 }
 
}

大概测试了下,功能都正常:

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

本章完结,OK。

另附:在后面章节中将会实现帐户功能,因为这里主要是实现交易功能,像这里的账号,你只要知道他的名字,可以随便转账,这显然是不行的。文章来源地址https://www.toymoban.com/news/detail-810973.html

到了这里,关于PHP从零实现区块链(网页版四)交易1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于PHP后台微信二手交易小程序系统设计与实现(安装部署+源码+文档)

     博主介绍 :黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩。 项目配有对应开发文档、

    2024年03月27日
    浏览(89)
  • PHP毕业设计课题选题(16)基于web网页网站PHP文件共享网站系统设计与实现

     博主介绍 :《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、PPT、论文模版

    2024年02月08日
    浏览(47)
  • PHP毕业设计课题选题(14)基于web网页网站PHP在线教室视频点播学习系统设计与实现

     博主介绍 :《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、PPT、论文模版

    2024年02月08日
    浏览(51)
  • Java基于区块链的物联网数据交易+46193(免费领源码)可做计算机毕业设计JAVA、PHP、爬虫、APP、小程序、C#、C++、python、数据可视化、大数据、全套文案

    SSM基于区块链的物联网数据交易 系    院 XXXX 学科门类 XXX 专    业  XXX 班级 XXX 学    号 XXX 姓    名 XXX 指导教师 XXX 教师职称 XXX 2022 年 12 月 4 日         物联网技术作为继互联网技术后新一代的通信信息集成应用的典范,其巨大的应用前景受到了学术界和政商界

    2024年02月04日
    浏览(45)
  • 微信小程序之二手车交易商城平台的设计与实现 后台php+mysql(附论文 源码 讲解)

    摘 要 在移动互联网的迅速发展推进下,微信成了人们生活中不可缺少的一款信息交流和沟通平台。而微信小程序的推出,便得现在人们在日常生活中更多的是通过手机微信平台进行安装各种各样的APP小程序来满足个人所需。二手车微信小程序是基于满足当前人们对二手车买

    2024年02月06日
    浏览(53)
  • PHP实践:手把手微信公众号网页授权登录功能实现

    🏆作者简介,黑夜开发者,全栈领域新星创作者✌,CSDN博客专家,阿里云社区专家博主,2023年6月CSDN上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于PHP专栏:PHP进阶实战教程。 🏆另有专栏PHP入门基础教程,希望各位大佬

    2024年02月12日
    浏览(52)
  • 【毕业设计】微信小程序之二手车交易商城平台的设计与实现 后台php+mysql(附论文 源码 讲解)

    摘 要 在移动互联网的迅速发展推进下,微信成了人们生活中不可缺少的一款信息交流和沟通平台。而微信小程序的推出,便得现在人们在日常生活中更多的是通过手机微信平台进行安装各种各样的APP小程序来满足个人所需。二手车微信小程序是基于满足当前人们对二手车买

    2024年02月07日
    浏览(57)
  • 从零开始的PHP开发逆天路——语法

    PHP 脚本在服务器上执行,然后将纯 HTML 结果发送回浏览器。 PHP 脚本可以放在文档中的任何位置。 PHP 脚本以 ?php 开始,以 ? 结束: ?php // PHP 代码 ? PHP 文件的默认文件扩展名是 .php。 PHP 文件通常包含 HTML 标签和一些 PHP 脚本代码。 向浏览器输出文本 \\\"Hello World!\\\": !DOCTYPE htm

    2024年04月17日
    浏览(40)
  • 如何使用PHP开发网页定时刷新功能

    如何使用PHP开发网页定时刷新功能 随着互联网的发展,越来越多的网站需要实时更新显示数据。而实时刷新页面是一种常见的需求,它可以让用户在不刷新整个页面的情况下获得最新的数据。本文将介绍如何使用PHP开发网页定时刷新功能,并提供代码示例。 1.使用Meta标签定

    2024年02月12日
    浏览(60)
  • HTML、PHP实战:搭建一个网页登录页面。

    一、实验环境。 MySQL5.7.26 FTP0.9.60 Apache2.4.39 我这里用的是PHPstudy小皮一键搭建的。 数据库  二、登录页面。 登录页面前端代码 文件名:denglu.html 登录页面后端代码 文件名:denglu.php 使用127.0.0.1跟文件名访问网页。 效果演示   可以看到当我们输入用户名:zhangsan 密码:12345

    2024年02月06日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包