UNPKG

8.17 kBJavaScriptView Raw
1const ethUtil = require('ethereumjs-util')
2const Tx = require('ethereumjs-tx')
3const Trie = require('merkle-patricia-tree')
4const BN = ethUtil.BN
5const rlp = ethUtil.rlp
6const async = require('async')
7const BlockHeader = require('./header')
8const params = require('ethereum-common/params.json')
9
10/**
11 * Creates a new block object
12 * @constructor the raw serialized or the deserialized block.
13 * @param {Array|Buffer|Object} data
14 * @prop {Header} header the block's header
15 * @prop {Array.<Header>} uncleList an array of uncle headers
16 * @prop {Array.<Buffer>} raw an array of buffers containing the raw blocks.
17 */
18var Block = module.exports = function (data) {
19 this.transactions = []
20 this.uncleHeaders = []
21 this._inBlockChain = false
22 this.txTrie = new Trie()
23
24 Object.defineProperty(this, 'raw', {
25 get: function () {
26 return this.serialize(false)
27 }
28 })
29
30 var rawTransactions, rawUncleHeaders
31
32 // defaults
33 if (!data) {
34 data = [[], [], []]
35 }
36
37 if (Buffer.isBuffer(data)) {
38 data = rlp.decode(data)
39 }
40
41 if (Array.isArray(data)) {
42 this.header = new BlockHeader(data[0])
43 rawTransactions = data[1]
44 rawUncleHeaders = data[2]
45 } else {
46 this.header = new BlockHeader(data.header)
47 rawTransactions = data.transactions || []
48 rawUncleHeaders = data.uncleHeaders || []
49 }
50
51 // parse uncle headers
52 for (var i = 0; i < rawUncleHeaders.length; i++) {
53 this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i]))
54 }
55
56 var homestead = this.isHomestead()
57 // parse transactions
58 for (i = 0; i < rawTransactions.length; i++) {
59 var tx = new Tx(rawTransactions[i])
60 tx._homestead = homestead
61 this.transactions.push(tx)
62 }
63}
64
65Block.Header = BlockHeader
66
67/**
68 * Produces a hash the RLP of the block
69 * @method hash
70 */
71Block.prototype.hash = function () {
72 return this.header.hash()
73}
74
75/**
76 * Determines if a given block is the genesis block
77 * @method isGenisis
78 * @return Boolean
79 */
80Block.prototype.isGenesis = function () {
81 return this.header.isGenesis()
82}
83
84/**
85 * Determines if a given block part of homestead or not
86 * @method isHomestead
87 * @return Boolean
88 */
89Block.prototype.isHomestead = function () {
90 return this.header.isHomestead()
91}
92
93/**
94 * Determines if a given block part of homestead reprice or not
95 * @method isHomesteadReprice
96 * @return Boolean
97 */
98Block.prototype.isHomesteadReprice = function () {
99 return this.header.isHomesteadReprice()
100}
101
102/**
103 * turns the block in to the canonical genesis block
104 * @method setGenesisParams
105 */
106Block.prototype.setGenesisParams = function () {
107 this.header.gasLimit = params.genesisGasLimit.v
108 this.header.difficulty = params.genesisDifficulty.v
109 this.header.extraData = params.genesisExtraData.v
110 this.header.nonce = params.genesisNonce.v
111 this.header.stateRoot = params.genesisStateRoot.v
112 this.header.number = new Buffer([])
113}
114
115/**
116 * Produces a serialization of the block.
117 * @method serialize
118 * @param {Boolean} rlpEncode whether to rlp encode the block or not
119 */
120Block.prototype.serialize = function (rlpEncode) {
121 var raw = [this.header.raw, [],
122 []
123 ]
124
125 // rlpEnode defaults to true
126 if (typeof rlpEncode === 'undefined') {
127 rlpEncode = true
128 }
129
130 this.transactions.forEach(function (tx) {
131 raw[1].push(tx.raw)
132 })
133
134 this.uncleHeaders.forEach(function (uncle) {
135 raw[2].push(uncle.raw)
136 })
137
138 return rlpEncode ? rlp.encode(raw) : raw
139}
140
141/**
142 * Generate transaction trie. The tx trie must be generated before the transaction trie can
143 * be validated with `validateTransactionTrie`
144 * @method genTxTrie
145 * @param {Function} cb the callback
146 */
147Block.prototype.genTxTrie = function (cb) {
148 var i = 0
149 var self = this
150
151 async.eachSeries(this.transactions, function (tx, done) {
152 self.txTrie.put(rlp.encode(i), tx.serialize(), done)
153 i++
154 }, cb)
155}
156
157/**
158 * Validates the transaction trie
159 * @method validateTransactionTrie
160 * @return {Boolean}
161 */
162Block.prototype.validateTransactionsTrie = function () {
163 var txT = this.header.transactionsTrie.toString('hex')
164 if (this.transactions.length) {
165 return txT === this.txTrie.root.toString('hex')
166 } else {
167 return txT === ethUtil.SHA3_RLP.toString('hex')
168 }
169}
170
171/**
172 * Validates the transactions
173 * @method validateTransactions
174 * @param {Boolean} [stringError=false] whether to return a string with a dscription of why the validation failed or return a Bloolean
175 * @return {Boolean}
176 */
177Block.prototype.validateTransactions = function (stringError) {
178 var errors = []
179
180 this.transactions.forEach(function (tx, i) {
181 var error = tx.validate(true)
182 if (error) {
183 errors.push(error + ' at tx ' + i)
184 }
185 })
186
187 if (stringError === undefined || stringError === false) {
188 return errors.length === 0
189 } else {
190 return arrayToString(errors)
191 }
192}
193
194/**
195 * Validates the entire block. Returns a string to the callback if block is invalid
196 * @method validate
197 * @param {BlockChain} blockChain the blockchain that this block wants to be part of
198 * @param {Function} cb the callback which is given a `String` if the block is not valid
199 */
200Block.prototype.validate = function (blockChain, cb) {
201 var self = this
202 var errors = []
203
204 async.parallel([
205 // validate uncles
206 self.validateUncles.bind(self, blockChain),
207 // validate block
208 self.header.validate.bind(self.header, blockChain),
209 // generate the transaction trie
210 self.genTxTrie.bind(self)
211 ], function (err) {
212 if (err) {
213 errors.push(err)
214 }
215
216 if (!self.validateTransactionsTrie()) {
217 errors.push('invalid transaction true')
218 }
219
220 var txErrors = self.validateTransactions(true)
221 if (txErrors !== '') {
222 errors.push(txErrors)
223 }
224
225 if (!self.validateUnclesHash()) {
226 errors.push('invild uncle hash')
227 }
228
229 cb(arrayToString(errors))
230 })
231}
232
233/**
234 * Validates the uncle's hash
235 * @method validateUncleHash
236 * @return {Boolean}
237 */
238Block.prototype.validateUnclesHash = function () {
239 var raw = []
240 this.uncleHeaders.forEach(function (uncle) {
241 raw.push(uncle.raw)
242 })
243
244 raw = rlp.encode(raw)
245 return ethUtil.sha3(raw).toString('hex') === this.header.uncleHash.toString('hex')
246}
247
248/**
249 * Validates the uncles that are in the block if any. Returns a string to the callback if uncles are invalid
250 * @method validateUncles
251 * @param {Blockchain} blockChaina an instance of the Blockchain
252 * @param {Function} cb the callback
253 */
254Block.prototype.validateUncles = function (blockChain, cb) {
255 if (this.isGenesis()) {
256 return cb()
257 }
258
259 var self = this
260
261 if (self.uncleHeaders.length > 2) {
262 return cb('too many uncle headers')
263 }
264
265 var uncleHashes = self.uncleHeaders.map(function (header) {
266 return header.hash().toString('hex')
267 })
268
269 if (!((new Set(uncleHashes)).size === uncleHashes.length)) {
270 return cb('dublicate unlces')
271 }
272
273 async.each(self.uncleHeaders, function (uncle, cb2) {
274 var height = new BN(self.header.number)
275 async.parallel([
276 uncle.validate.bind(uncle, blockChain, height),
277 // check to make sure the uncle is not already in the blockchain
278 function (cb3) {
279 blockChain.getDetails(uncle.hash(), function (err, blockInfo) {
280 // TODO: remove uncles from BC
281 if (blockInfo && blockInfo.isUncle) {
282 cb3(err || 'uncle already included')
283 } else {
284 cb3()
285 }
286 })
287 }
288 ], cb2)
289 }, cb)
290}
291
292/**
293 * Converts the block toJSON
294 * @method toJSON
295 * @param {Bool} labeled whether to create an labeled object or an array
296 * @return {Object}
297 */
298Block.prototype.toJSON = function (labeled) {
299 if (labeled) {
300 var obj = {
301 header: this.header.toJSON(true),
302 transactions: [],
303 uncleHeaders: []
304 }
305
306 this.transactions.forEach(function (tx) {
307 obj.transactions.push(tx.toJSON(labeled))
308 })
309
310 this.uncleHeaders.forEach(function (uh) {
311 obj.uncleHeaders.push(uh.toJSON())
312 })
313 return obj
314 } else {
315 return ethUtil.baToJSON(this.raw)
316 }
317}
318
319function arrayToString (array) {
320 try {
321 return array.reduce(function (str, err) {
322 if (str) {
323 str += ' '
324 }
325 return str + err
326 })
327 } catch (e) {
328 return ''
329 }
330}