Completed
Pull Request — master (#546)
by thomas
75:01 queued 05:00
created

TransactionTest::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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