Skip to content
This repository was archived by the owner on Jan 25, 2024. It is now read-only.

Commit 5e171d9

Browse files
author
Mark Erhardt
committed
Merge branch 'BG-16466.backport-bufferutils'
2 parents 014e0e3 + 79d7700 commit 5e171d9

File tree

10 files changed

+718
-528
lines changed

10 files changed

+718
-528
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"scripts": {
2020
"coverage-report": "nyc report --reporter=lcov",
2121
"coverage-html": "nyc report --reporter=html",
22-
"coverage": "BITGO_UTXO_LIB_TEST_EXPECTED_COUNT=3436 nyc --check-coverage --branches 90 --functions 90 mocha --recursive",
22+
"coverage": "BITGO_UTXO_LIB_TEST_EXPECTED_COUNT=3442 nyc --check-coverage --branches 90 --functions 90 mocha --recursive",
2323
"integration": "mocha test/integration/",
2424
"standard": "standard",
2525
"test": "npm run standard && npm run coverage",

src/block.js

Lines changed: 35 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var bcrypto = require('./crypto')
33
var fastMerkleRoot = require('merkle-lib/fastRoot')
44
var typeforce = require('typeforce')
55
var types = require('./types')
6+
var bufferutils = require('./bufferutils')
67
var varuint = require('varuint-bitcoin')
78
var networks = require('./networks')
89
var coins = require('./coins')
@@ -33,57 +34,35 @@ Block.fromBuffer = function (buffer, network) {
3334
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)')
3435
network = network || networks.bitcoin
3536

36-
var offset = 0
37-
function readSlice (n) {
38-
offset += n
39-
return buffer.slice(offset - n, offset)
40-
}
41-
42-
function readUInt32 () {
43-
var i = buffer.readUInt32LE(offset)
44-
offset += 4
45-
return i
46-
}
47-
48-
function readInt32 () {
49-
var i = buffer.readInt32LE(offset)
50-
offset += 4
51-
return i
52-
}
53-
54-
function readVarInt () {
55-
var vi = varuint.decode(buffer, offset)
56-
offset += varuint.decode.bytes
57-
return vi
58-
}
37+
const bufferReader = new bufferutils.BufferReader(buffer)
5938

6039
var block = new Block(network)
61-
block.version = readInt32()
62-
block.prevHash = readSlice(32)
63-
block.merkleRoot = readSlice(32)
40+
block.version = bufferReader.readInt32()
41+
block.prevHash = bufferReader.readSlice(32)
42+
block.merkleRoot = bufferReader.readSlice(32)
6443
if (coins.isZcash(network)) {
65-
block.finalSaplingRoot = readSlice(32)
44+
block.finalSaplingRoot = bufferReader.readSlice(32)
6645
}
67-
block.timestamp = readUInt32()
68-
block.bits = readUInt32()
46+
block.timestamp = bufferReader.readUInt32()
47+
block.bits = bufferReader.readUInt32()
6948
if (coins.isZcash(network)) {
70-
block.nonce = readSlice(32)
71-
block.solutionSize = readVarInt()
72-
block.solution = readSlice(1344)
49+
block.nonce = bufferReader.readSlice(32)
50+
block.solutionSize = bufferReader.readVarInt()
51+
block.solution = bufferReader.readSlice(1344)
7352
} else {
7453
// Not sure sure why the nonce is read as UInt 32 and not as a slice
75-
block.nonce = readUInt32()
54+
block.nonce = bufferReader.readUInt32()
7655
}
7756

78-
if (buffer.length === 80) return block
57+
if (bufferReader.buffer.length === 80) return block
7958

8059
function readTransaction () {
81-
var tx = Transaction.fromBuffer(buffer.slice(offset), network, true)
82-
offset += tx.byteLength()
60+
var tx = Transaction.fromBuffer(buffer.slice(bufferReader.offset), network, true)
61+
bufferReader.offset += tx.byteLength()
8362
return tx
8463
}
8564

86-
var nTransactions = readVarInt()
65+
var nTransactions = bufferReader.readVarInt()
8766
block.transactions = []
8867

8968
for (var i = 0; i < nTransactions; ++i) {
@@ -135,49 +114,38 @@ Block.prototype.getUTCDate = function () {
135114
// TODO: buffer, offset compatibility
136115
Block.prototype.toBuffer = function (headersOnly) {
137116
var buffer = Buffer.allocUnsafe(this.byteLength(headersOnly))
117+
var bufferWriter = new bufferutils.BufferWriter(buffer)
138118

139-
var offset = 0
140-
function writeSlice (slice) {
141-
slice.copy(buffer, offset)
142-
offset += slice.length
143-
}
144-
145-
function writeInt32 (i) {
146-
buffer.writeInt32LE(i, offset)
147-
offset += 4
148-
}
149-
function writeUInt32 (i) {
150-
buffer.writeUInt32LE(i, offset)
151-
offset += 4
152-
}
153-
154-
writeInt32(this.version)
155-
writeSlice(this.prevHash)
156-
writeSlice(this.merkleRoot)
119+
bufferWriter.writeInt32(this.version)
120+
bufferWriter.writeSlice(this.prevHash)
121+
bufferWriter.writeSlice(this.merkleRoot)
157122
if (coins.isZcash(this.network)) {
158-
writeSlice(this.finalSaplingRoot)
123+
bufferWriter.writeSlice(this.finalSaplingRoot)
159124
}
160-
writeUInt32(this.timestamp)
161-
writeUInt32(this.bits)
125+
bufferWriter.writeUInt32(this.timestamp)
126+
bufferWriter.writeUInt32(this.bits)
162127
if (coins.isZcash(this.network)) {
163-
writeSlice(this.nonce)
164-
varuint.encode(this.solutionSize, buffer, offset)
165-
offset += varuint.encode.bytes
166-
writeSlice(this.solution)
128+
bufferWriter.writeSlice(this.nonce)
129+
// TODO: use writeVarInt
130+
varuint.encode(this.solutionSize, bufferWriter.buffer, bufferWriter.offset)
131+
bufferWriter.offset += varuint.encode.bytes
132+
bufferWriter.writeSlice(this.solution)
167133
} else {
168134
// Not sure sure why the nonce is interpreted as UInt 32 and not a slice in bitcoin
169-
writeUInt32(this.nonce)
135+
bufferWriter.writeUInt32(this.nonce)
170136
}
171137

172138
if (headersOnly || !this.transactions) return buffer
173139

174-
varuint.encode(this.transactions.length, buffer, offset)
175-
offset += varuint.encode.bytes
140+
// TODO: use writeVarInt
141+
varuint.encode(this.transactions.length, bufferWriter.buffer, bufferWriter.offset)
142+
bufferWriter.offset += varuint.encode.bytes
176143

144+
// TODO: use writeVarInt
177145
this.transactions.forEach(function (tx) {
178146
var txSize = tx.byteLength() // TODO: extract from toBuffer?
179-
tx.toBuffer(buffer, offset)
180-
offset += txSize
147+
tx.toBuffer(bufferWriter.buffer, bufferWriter.offset)
148+
bufferWriter.offset += txSize
181149
})
182150

183151
return buffer

src/buffer_writer.js

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/bufferutils.js

Lines changed: 135 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,141 @@
1+
/* eslint-disable */
2+
const types = require('./types');
3+
const typeforce = require('typeforce');
4+
const varuint = require('varuint-bitcoin');
15
// https://github.com/feross/buffer/blob/master/index.js#L1127
2-
function verifuint (value, max) {
3-
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number')
4-
if (value < 0) throw new Error('specified a negative value for writing an unsigned value')
5-
if (value > max) throw new Error('RangeError: value out of range')
6-
if (Math.floor(value) !== value) throw new Error('value has a fractional component')
6+
function verifuint(value, max) {
7+
if (typeof value !== 'number')
8+
throw new Error('cannot write a non-number as a number');
9+
if (value < 0)
10+
throw new Error('specified a negative value for writing an unsigned value');
11+
if (value > max)
12+
throw new Error('RangeError: value out of range');
13+
if (Math.floor(value) !== value)
14+
throw new Error('value has a fractional component');
715
}
8-
9-
function readUInt64LE (buffer, offset) {
10-
var a = buffer.readUInt32LE(offset)
11-
var b = buffer.readUInt32LE(offset + 4)
12-
b *= 0x100000000
13-
14-
verifuint(b + a, 0x001fffffffffffff)
15-
16-
return b + a
16+
function readUInt64LE(buffer, offset) {
17+
const a = buffer.readUInt32LE(offset);
18+
let b = buffer.readUInt32LE(offset + 4);
19+
b *= 0x100000000;
20+
verifuint(b + a, 0x001fffffffffffff);
21+
return b + a;
1722
}
18-
19-
function readInt64LE (buffer, offset) {
20-
var a = buffer.readUInt32LE(offset)
21-
var b = buffer.readInt32LE(offset + 4)
22-
b *= 0x100000000
23-
24-
return b + a
23+
function writeUInt64LE(buffer, value, offset) {
24+
verifuint(value, 0x001fffffffffffff);
25+
buffer.writeInt32LE(value & -1, offset);
26+
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
27+
return offset + 8;
2528
}
26-
27-
function writeUInt64LE (buffer, value, offset) {
28-
verifuint(value, 0x001fffffffffffff)
29-
30-
buffer.writeInt32LE(value & -1, offset)
31-
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4)
32-
return offset + 8
29+
function reverseBuffer(buffer) {
30+
if (buffer.length < 1)
31+
return buffer;
32+
let j = buffer.length - 1;
33+
let tmp = 0;
34+
for (let i = 0; i < buffer.length / 2; i++) {
35+
tmp = buffer[i];
36+
buffer[i] = buffer[j];
37+
buffer[j] = tmp;
38+
j--;
39+
}
40+
return buffer;
3341
}
34-
35-
module.exports = {
36-
readUInt64LE: readUInt64LE,
37-
readInt64LE: readInt64LE,
38-
writeUInt64LE: writeUInt64LE
42+
/**
43+
* Helper class for serialization of bitcoin data types into a pre-allocated buffer.
44+
*/
45+
class BufferWriter {
46+
constructor(buffer, offset = 0) {
47+
this.buffer = buffer;
48+
this.offset = offset;
49+
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
50+
}
51+
writeUInt8(i) {
52+
this.offset = this.buffer.writeUInt8(i, this.offset);
53+
}
54+
writeInt32(i) {
55+
this.offset = this.buffer.writeInt32LE(i, this.offset);
56+
}
57+
writeUInt32(i) {
58+
this.offset = this.buffer.writeUInt32LE(i, this.offset);
59+
}
60+
writeUInt64(i) {
61+
this.offset = writeUInt64LE(this.buffer, i, this.offset);
62+
}
63+
writeVarInt(i) {
64+
varuint.encode(i, this.buffer, this.offset);
65+
this.offset += varuint.encode.bytes;
66+
}
67+
writeSlice(slice) {
68+
if (this.buffer.length < this.offset + slice.length) {
69+
throw new Error('Cannot write slice out of bounds');
70+
}
71+
this.offset += slice.copy(this.buffer, this.offset);
72+
}
73+
writeVarSlice(slice) {
74+
this.writeVarInt(slice.length);
75+
this.writeSlice(slice);
76+
}
77+
writeVector(vector) {
78+
this.writeVarInt(vector.length);
79+
vector.forEach((buf) => this.writeVarSlice(buf));
80+
}
81+
}
82+
/**
83+
* Helper class for reading of bitcoin data types from a buffer.
84+
*/
85+
class BufferReader {
86+
constructor(buffer, offset = 0) {
87+
this.buffer = buffer;
88+
this.offset = offset;
89+
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
90+
}
91+
readUInt8() {
92+
const result = this.buffer.readUInt8(this.offset);
93+
this.offset++;
94+
return result;
95+
}
96+
readInt32() {
97+
const result = this.buffer.readInt32LE(this.offset);
98+
this.offset += 4;
99+
return result;
100+
}
101+
readUInt32() {
102+
const result = this.buffer.readUInt32LE(this.offset);
103+
this.offset += 4;
104+
return result;
105+
}
106+
readUInt64() {
107+
const result = readUInt64LE(this.buffer, this.offset);
108+
this.offset += 8;
109+
return result;
110+
}
111+
readVarInt() {
112+
const vi = varuint.decode(this.buffer, this.offset);
113+
this.offset += varuint.decode.bytes;
114+
return vi;
115+
}
116+
readSlice(n) {
117+
if (this.buffer.length < this.offset + n) {
118+
throw new Error('Cannot read slice out of bounds');
119+
}
120+
const result = this.buffer.slice(this.offset, this.offset + n);
121+
this.offset += n;
122+
return result;
123+
}
124+
readVarSlice() {
125+
return this.readSlice(this.readVarInt());
126+
}
127+
readVector() {
128+
const count = this.readVarInt();
129+
const vector = [];
130+
for (let i = 0; i < count; i++)
131+
vector.push(this.readVarSlice());
132+
return vector;
133+
}
39134
}
135+
module.exports = {
136+
readUInt64LE,
137+
writeUInt64LE,
138+
reverseBuffer,
139+
BufferWriter,
140+
BufferReader,
141+
};

src/forks/zcash/bufferutils.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { BufferReader } = require('../../bufferutils')
2+
3+
class ZcashBufferReader extends BufferReader {
4+
readInt64 () {
5+
const a = this.buffer.readUInt32LE(this.offset)
6+
let b = this.buffer.readInt32LE(this.offset + 4)
7+
b *= 0x100000000
8+
this.offset += 8
9+
return b + a
10+
}
11+
}
12+
13+
module.exports = { ZcashBufferReader }

0 commit comments

Comments
 (0)