Completed
Pull Request — master (#591)
by thomas
22:54 queued 09:12
created

TransactionTest::testCases()   D

Complexity

Conditions 14
Paths 396

Size

Total Lines 70
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 43
nc 396
nop 1
dl 0
loc 70
rs 4.1959
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\RpcTest;
6
7
use BitWasp\Bitcoin\Key\PrivateKeyFactory;
8
use BitWasp\Bitcoin\Network\NetworkFactory;
9
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
10
use BitWasp\Bitcoin\Script\ScriptFactory;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Bitcoin\Transaction\Factory\SignData;
13
use BitWasp\Bitcoin\Transaction\Factory\Signer;
14
use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
15
use BitWasp\Bitcoin\Transaction\OutPoint;
16
use BitWasp\Bitcoin\Transaction\TransactionFactory;
17
use BitWasp\Bitcoin\Transaction\TransactionOutput;
18
use BitWasp\Bitcoin\Utxo\Utxo;
19
use BitWasp\Buffertools\Buffer;
20
21
class TransactionTest extends AbstractTestCase
22
{
23
    /**
24
     * @var RegtestBitcoinFactory
25
     */
26
    private $rpcFactory;
27
28
    public function __construct($name = null, array $data = [], $dataName = '')
29
    {
30
        parent::__construct($name, $data, $dataName);
31
32
        static $rpcFactory = null;
33
        if (null === $rpcFactory) {
34
            $rpcFactory = new RegtestBitcoinFactory();
35
        }
36
        $this->rpcFactory = $rpcFactory;
37
    }
38
39
    /**
40
     * Produces scripts for signing
41
     * @return array
42
     */
43
    public function getScriptVectors(): array
44
    {
45
        return $this->jsonDataFile('signer_fixtures.json')['valid'];
46
    }
47
48
    /**
49
     * Produces ALL vectors
50
     * @return array
51
     */
52
    public function getVectors(): array
53
    {
54
        $vectors = [];
55
        foreach ($this->getScriptVectors() as $fixture) {
56
            if ($fixture['id'] === "1a47e53c26efe81aaf7fceedf447d965b0d5cb26b35d3a9a2b32aa89ae9a979f") {
57
                continue;
58
            }
59
            $vectors[] = [$this->stripTestData($fixture)];
60
        }
61
62
        return $vectors;
63
    }
64
65
    /**
66
     * @param array $fixture
67
     * @return array
68
     */
69
    public function stripTestData(array $fixture): array
70
    {
71
        foreach (['hex'] as $key) {
72
            if (array_key_exists($key, $fixture)) {
73
                unset($fixture[$key]);
74
            }
75
        }
76
        foreach ($fixture['raw']['ins'] as &$input) {
77
            unset($input['hash']);
78
            unset($input['index']);
79
            unset($input['value']);
80
        }
81
        return $fixture;
82
    }
83
84
    /**
85
     * @param ScriptInterface $script
86
     * @param int $value
87
     * @return Utxo
88
     */
89
    public function fundOutput(RpcServer $server, ScriptInterface $script, $value = 100000000): Utxo
90
    {
91
        $chainInfo = $server->makeRpcRequest('getblockchaininfo');
92
        $bestHeight = $chainInfo['result']['blocks'];
93
94
        while ($bestHeight < 150 || $chainInfo['result']['bip9_softforks']['segwit']['status'] !== 'active') {
95
            // ought to finish in 1!
96
            $server->makeRpcRequest("generate", [435]);
97
            $chainInfo = $server->makeRpcRequest('getblockchaininfo');
98
            $bestHeight = $chainInfo['result']['blocks'];
99
        }
100
101
        $builder = new TxBuilder();
102
        $builder->output($value, $script);
103
        $hex = $builder->get()->getHex();
104
105
        $result = $server->makeRpcRequest('fundrawtransaction', [$hex, ['feeRate'=>0.0001]]);
106
        $unsigned = $result['result']['hex'];
107
        $result = $server->makeRpcRequest('signrawtransaction', [$unsigned]);
108
        $signedHex = $result['result']['hex'];
109
        $signed = TransactionFactory::fromHex($signedHex);
110
        $outIdx = -1;
111
        foreach ($signed->getOutputs() as $i => $output) {
112
            if ($output->getScript()->equals($script)) {
113
                $outIdx = $i;
114
            }
115
        }
116
117
        if ($outIdx === -1) {
118
            throw new \RuntimeException("Sanity check failed, should have found the output we funded");
119
        }
120
121
        $result = $server->makeRpcRequest('sendrawtransaction', [$signedHex]);
122
        $txid = $result['result'];
123
        $server->makeRpcRequest("generate", [1]);
124
125
        return new Utxo(new OutPoint(Buffer::hex($txid), $outIdx), new TransactionOutput($value, $script));
126
    }
127
128
    /**
129
     * @param array $fixture
130
     * @dataProvider getVectors
131
     */
132
    public function testCases(array $fixture)
133
    {
134
        $bitcoind = $this->rpcFactory->startBitcoind();
135
        $this->assertTrue($bitcoind->isRunning());
136
137
        $defaultPolicy = Interpreter::VERIFY_NONE | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_WITNESS | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY;
138
        ;
139
        $txBuilder = new TxBuilder();
140
        if (array_key_exists('version', $fixture['raw'])) {
141
            $txBuilder->version((int) $fixture['raw']['version']);
142
        }
143
144
        $totalOut = 12345;
145
        foreach ($fixture['raw']['outs'] as $output) {
146
            $value = (int) $output['value'];
147
            $txBuilder->output($value, ScriptFactory::fromHex($output['script']));
148
            $totalOut += $value;
149
        }
150
151
        /**
152
         * @var SignData[] $signDatas
153
         * @var Utxo[] $utxos
154
         */
155
        $signDatas = [];
156
        $utxos = [];
157
        foreach ($fixture['raw']['ins'] as $input) {
158
            $scriptPubKey = ScriptFactory::fromHex($input['scriptPubKey']);
159
160
            $value = array_key_exists('value', $input) ? (int) $input['value'] : $totalOut;
161
            $utxo = $this->fundOutput($bitcoind, $scriptPubKey, $value);
162
163
            $sequence = array_key_exists('sequence', $input) ? (int) $input['sequence'] : 0xffffffff;
164
            $txBuilder->spendOutPoint($utxo->getOutPoint(), null, $sequence);
165
166
            $signData = new SignData();
167
            if (array_key_exists('redeemScript', $input) && "" !== $input['redeemScript']) {
168
                $signData->p2sh(ScriptFactory::fromHex($input['redeemScript']));
169
            }
170
            if (array_key_exists('witnessScript', $input) && "" !== $input['witnessScript']) {
171
                $signData->p2wsh(ScriptFactory::fromHex($input['witnessScript']));
172
            }
173
174
            $policy = array_key_exists('signaturePolicy', $fixture) ? $this->getScriptFlagsFromString($fixture['signaturePolicy']) : $defaultPolicy;
175
            $signData->signaturePolicy($policy);
176
            $signDatas[] = $signData;
177
178
            $utxos[] = $utxo;
179
        }
180
181
        $txBuilder->locktime(isset($fixture['raw']['locktime']) ? $fixture['raw']['locktime'] : 0);
182
183
        $signer = new Signer($txBuilder->get());
184
        foreach ($fixture['raw']['ins'] as $i => $input) {
185
            $iSigner = $signer->input($i, $utxos[$i]->getOutput(), $signDatas[$i]);
186
            foreach ($input['keys'] as $key) {
187
                $priv = PrivateKeyFactory::fromWif($key['key'], null, NetworkFactory::bitcoinTestnet());
188
                $sigHashType = $key['sigHashType'];
189
                $iSigner->sign($priv, $sigHashType);
190
            }
191
192
            $this->assertTrue($iSigner->isFullySigned());
193
        }
194
195
        $tx = $signer->get();
196
        $result = $bitcoind->makeRpcRequest('sendrawtransaction', [$tx->getHex(), true]);
197
        $this->assertEquals(null, $result['error']);
198
        
199
        $txid = $result['result'];
200
        $this->assertEquals(64, strlen($txid));
201
202
        $bitcoind->destroy();
203
    }
204
}
205