Completed
Pull Request — master (#542)
by thomas
72:40
created

TransactionTest::stripTestData()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 6
nop 1
dl 0
loc 14
rs 9.2
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
     * Check tests are being run against regtest
24
     */
25
    public function testIfRegtest()
26
    {
27
        $result = $this->makeRpcRequest("getblockchaininfo");
28
        $this->assertInternalType('array', $result);
29
        $this->assertArrayHasKey('result', $result);
30
        $this->assertArrayHasKey('chain', $result['result']);
31
        $this->assertEquals('regtest', $result['result']['chain']);
32
    }
33
34
    /**
35
     * Produces scripts for signing
36
     * @return array
37
     */
38
    public function getScriptVectors()
39
    {
40
        return $this->jsonDataFile('signer_fixtures.json')['valid'];
41
    }
42
43
    /**
44
     * Produces ALL vectors
45
     * @return array
46
     */
47
    public function getVectors()
48
    {
49
        $vectors = [];
50
        foreach ($this->getScriptVectors() as $fixture) {
51
            $vectors[] = [$this->stripTestData($fixture)];
52
        }
53
54
        return $vectors;
55
    }
56
57
    /**
58
     * @param array $fixture
59
     * @return array
60
     */
61
    public function stripTestData(array $fixture)
62
    {
63
        foreach (['hex'] as $key) {
64
            if (array_key_exists($key, $fixture)) {
65
                unset($fixture[$key]);
66
            }
67
        }
68
        foreach ($fixture['raw']['ins'] as &$input) {
69
            unset($input['hash']);
70
            unset($input['index']);
71
            unset($input['value']);
72
        }
73
        return $fixture;
74
    }
75
76
    /**
77
     * @param ScriptInterface $script
78
     * @param int $value
79
     * @return Utxo
80
     */
81
    public function fundOutput(ScriptInterface $script, $value = 100000000)
82
    {
83
        $chainInfo = $this->makeRpcRequest('getblockchaininfo');
84
        $bestHeight = $chainInfo['result']['blocks'];
85
86
        while($bestHeight < 150 || $chainInfo['result']['bip9_softforks']['segwit']['status'] !== 'active') {
87
            $this->makeRpcRequest("generate", [1]);
88
            $chainInfo = $this->makeRpcRequest('getblockchaininfo');
89
            $bestHeight = $chainInfo['result']['blocks'];
90
        }
91
92
        $builder = new TxBuilder();
93
        $builder->output($value, $script);
94
        $hex = $builder->get()->getHex();
95
96
        $result = $this->makeRpcRequest('fundrawtransaction', [$hex, ['feeRate'=>0.0001]]);
97
        $unsigned = $result['result']['hex'];
98
        $result = $this->makeRpcRequest('signrawtransaction', [$unsigned]);
99
        $signedHex = $result['result']['hex'];
100
        $signed = TransactionFactory::fromHex($signedHex);
101
        $outIdx = -1;
102
        foreach ($signed->getOutputs() as $i => $output) {
103
            if ($output->getScript()->equals($script)) {
104
                $outIdx = $i;
105
            }
106
        }
107
108
        if ($outIdx === -1) {
109
            throw new \RuntimeException("Sanity check failed, should have found the output we funded");
110
        }
111
112
        $result = $this->makeRpcRequest('sendrawtransaction', [$signedHex]);
113
        $txid = $result['result'];
114
        $this->makeRpcRequest("generate", [1]);
115
116
        return new Utxo(new OutPoint(Buffer::hex($txid), $outIdx), new TransactionOutput($value, $script));
117
    }
118
119
    /**
120
     * @param $fixture
121
     * @dataProvider getVectors
122
     */
123
    public function testCases($fixture)
124
    {
125
        $defaultPolicy = Interpreter::VERIFY_NONE | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_WITNESS | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY;;
126
        $txBuilder = new TxBuilder();
127
        if (array_key_exists('version', $fixture['raw'])) {
128
            $txBuilder->version((int) $fixture['raw']['version']);
129
        }
130
131
        $totalOut = 12345;
132
        foreach ($fixture['raw']['outs'] as $output) {
133
            $txBuilder->output($output['value'], ScriptFactory::fromHex($output['script']));
134
            $totalOut += $output['value'];
135
        }
136
137
        /**
138
         * @var SignData[] $signDatas
139
         * @var Utxo[] $utxos
140
         */
141
        $signDatas = [];
142
        $utxos = [];
143
        foreach ($fixture['raw']['ins'] as $input) {
144
            $scriptPubKey = ScriptFactory::fromHex($input['scriptPubKey']);
145
            if (array_key_exists('value', $input)) {
146
                echo "needs value: {$input['value']}\n";
147
            }
148
149
            $value = array_key_exists('value', $input) ? (int) $input['value'] : $totalOut;
150
            $utxo = $this->fundOutput($scriptPubKey, $value);
151
152
            $sequence = array_key_exists('sequence', $input) ? (int) $input['sequence'] : 0xffffffff;
153
            $txBuilder->spendOutPoint($utxo->getOutPoint(), null, $sequence);
154
155
            $signData = new SignData();
156
            if (array_key_exists('redeemScript', $input) && "" !== $input['redeemScript']) {
157
                $signData->p2sh(ScriptFactory::fromHex($input['redeemScript']));
158
            }
159
            if (array_key_exists('witnessScript', $input) && "" !== $input['witnessScript']) {
160
                $signData->p2wsh(ScriptFactory::fromHex($input['witnessScript']));
161
            }
162
163
            $policy = array_key_exists('signaturePolicy', $fixture) ? $this->getScriptFlagsFromString($fixture['signaturePolicy']) : $defaultPolicy;
164
            $signData->signaturePolicy($policy);
165
            $signDatas[] = $signData;
166
167
            $utxos[] = $utxo;
168
        }
169
170
        $txBuilder->locktime(isset($fixture['raw']['locktime']) ? $fixture['raw']['locktime'] : 0);
171
172
        $signer = new Signer($txBuilder->get());
173
        foreach ($fixture['raw']['ins'] as $i => $input) {
174
            $iSigner = $signer->input($i, $utxos[$i]->getOutput(), $signDatas[$i]);
175
            foreach ($input['keys'] as $key) {
176
                $priv = PrivateKeyFactory::fromWif($key['key'], null, NetworkFactory::bitcoinTestnet());
177
                $sigHashType = $key['sigHashType'];
178
                $iSigner->sign($priv, $sigHashType);
179
            }
180
181
            $this->assertTrue($iSigner->isFullySigned());
182
        }
183
184
        $tx = $signer->get();
185
        $result = $this->makeRpcRequest('sendrawtransaction', [$tx->getHex(), true]);
186
        $this->assertEquals(null, $result['error']);
187
        $txid = $result['result'];
188
        $this->assertEquals(64, strlen($txid));
189
    }
190
}
191