Completed
Pull Request — master (#546)
by thomas
38:05 queued 35:29
created

TransactionTest::fundOutput()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 37
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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