UNPKG

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