diff --git a/packages/common/package.json b/packages/common/package.json index 331d415a64..24a134645b 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -63,5 +63,7 @@ "email": "Holger.Drewes@gmail.com" } ], - "dependencies": {} + "dependencies": { + "crc-32": "^1.2.0" + } } diff --git a/packages/common/src/chains/goerli.json b/packages/common/src/chains/goerli.json index e8fd71a218..3ba30f3878 100644 --- a/packages/common/src/chains/goerli.json +++ b/packages/common/src/chains/goerli.json @@ -16,43 +16,53 @@ "hardforks": [ { "name": "chainstart", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "homestead", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "dao", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "tangerineWhistle", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "spuriousDragon", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "byzantium", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "constantinople", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "petersburg", - "block": 0 + "block": 0, + "forkHash": "0xa3f5ab08" }, { "name": "istanbul", - "block": 1561651 + "block": 1561651, + "forkHash": "0xc25efa5c" }, { "name": "berlin", - "block": null + "block": null, + "forkHash": null } ], "bootstrapNodes": [ diff --git a/packages/common/src/chains/kovan.json b/packages/common/src/chains/kovan.json index ed8b777e6d..932f3adb74 100644 --- a/packages/common/src/chains/kovan.json +++ b/packages/common/src/chains/kovan.json @@ -16,43 +16,53 @@ "hardforks": [ { "name": "chainstart", - "block": 0 + "block": 0, + "forkHash": "0x10ffe56" }, { "name": "homestead", - "block": 0 + "block": 0, + "forkHash": "0x10ffe56" }, { "name": "dao", - "block": 0 + "block": 0, + "forkHash": "0x10ffe56" }, { "name": "tangerineWhistle", - "block": 0 + "block": 0, + "forkHash": "0x10ffe56" }, { "name": "spuriousDragon", - "block": 0 + "block": 0, + "forkHash": "0x10ffe56" }, { "name": "byzantium", - "block": 5067000 + "block": 5067000, + "forkHash": "0x7f83c620" }, { "name": "constantinople", - "block": 9200000 + "block": 9200000, + "forkHash": "0xa94e3dc4" }, { "name": "petersburg", - "block": 10255201 + "block": 10255201, + "forkHash": "0x186874aa" }, { "name": "istanbul", - "block": 14111141 + "block": 14111141, + "forkHash": "0x7f6599a6" }, { "name": "berlin", - "block": null + "block": null, + "forkHash": null } ], "bootstrapNodes": [ diff --git a/packages/common/src/chains/mainnet.json b/packages/common/src/chains/mainnet.json index 43c44b7825..ea257b7679 100644 --- a/packages/common/src/chains/mainnet.json +++ b/packages/common/src/chains/mainnet.json @@ -16,47 +16,58 @@ "hardforks": [ { "name": "chainstart", - "block": 0 + "block": 0, + "forkHash": "0xfc64ec04" }, { "name": "homestead", - "block": 1150000 + "block": 1150000, + "forkHash": "0x97c2c34c" }, { "name": "dao", - "block": 1920000 + "block": 1920000, + "forkHash": "0x91d1f948" }, { "name": "tangerineWhistle", - "block": 2463000 + "block": 2463000, + "forkHash": "0x7a64da13" }, { "name": "spuriousDragon", - "block": 2675000 + "block": 2675000, + "forkHash": "0x3edd5b10" }, { "name": "byzantium", - "block": 4370000 + "block": 4370000, + "forkHash": "0xa00bc324" }, { "name": "constantinople", - "block": 7280000 + "block": 7280000, + "forkHash": "0x668db0af" }, { "name": "petersburg", - "block": 7280000 + "block": 7280000, + "forkHash": "0x668db0af" }, { "name": "istanbul", - "block": 9069000 + "block": 9069000, + "forkHash": "0x879d6e30" }, { "name": "muirGlacier", - "block": 9200000 + "block": 9200000, + "forkHash": "0xe029e991" }, { "name": "berlin", - "block": null + "block": null, + "forkHash": null } ], "bootstrapNodes": [ diff --git a/packages/common/src/chains/rinkeby.json b/packages/common/src/chains/rinkeby.json index 195c05f08c..38ba085bb6 100644 --- a/packages/common/src/chains/rinkeby.json +++ b/packages/common/src/chains/rinkeby.json @@ -16,43 +16,53 @@ "hardforks": [ { "name": "chainstart", - "block": 0 + "block": 0, + "forkHash": "0x3b8e0691" }, { "name": "homestead", - "block": 1 + "block": 1, + "forkHash": "0x60949295" }, { "name": "dao", - "block": null + "block": null, + "forkHash": null }, { "name": "tangerineWhistle", - "block": 2 + "block": 2, + "forkHash": "0x8bde40dd" }, { "name": "spuriousDragon", - "block": 3 + "block": 3, + "forkHash": "0xcb3a64bb" }, { "name": "byzantium", - "block": 1035301 + "block": 1035301, + "forkHash": "0x8d748b57" }, { "name": "constantinople", - "block": 3660663 + "block": 3660663, + "forkHash": "0xe49cab14" }, { "name": "petersburg", - "block": 4321234 + "block": 4321234, + "forkHash": "0xafec6b27" }, { "name": "istanbul", - "block": 5435345 + "block": 5435345, + "forkHash": "0xcbdb8838" }, { "name": "berlin", - "block": null + "block": null, + "forkHash": null } ], "bootstrapNodes": [ diff --git a/packages/common/src/chains/ropsten.json b/packages/common/src/chains/ropsten.json index a1ae2197af..e673dd2b93 100644 --- a/packages/common/src/chains/ropsten.json +++ b/packages/common/src/chains/ropsten.json @@ -16,47 +16,58 @@ "hardforks": [ { "name": "chainstart", - "block": 0 + "block": 0, + "forkHash": "0x30c7ddbc" }, { "name": "homestead", - "block": 0 + "block": 0, + "forkHash": "0x30c7ddbc" }, { "name": "dao", - "block": null + "block": null, + "forkHash": null }, { "name": "tangerineWhistle", - "block": 0 + "block": 0, + "forkHash": "0x30c7ddbc" }, { "name": "spuriousDragon", - "block": 10 + "block": 10, + "forkHash": "0x63760190" }, { "name": "byzantium", - "block": 1700000 + "block": 1700000, + "forkHash": "0x3ea159c7" }, { "name": "constantinople", - "block": 4230000 + "block": 4230000, + "forkHash": "0x97b544f3" }, { "name": "petersburg", - "block": 4939394 + "block": 4939394, + "forkHash": "0xd6e2149b" }, { "name": "istanbul", - "block": 6485846 + "block": 6485846, + "forkHash": "0x4bc66396" }, { "name": "muirGlacier", - "block": 7117117 + "block": 7117117, + "forkHash": "0x6727ef90" }, { "name": "berlin", - "block": null + "block": null, + "forkHash": null } ], "bootstrapNodes": [ diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 2b4a8a73f2..7fb9eb381d 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,3 +1,4 @@ +import { buf as crc32Buffer } from 'crc-32' import { chains as chainParams } from './chains' import { hardforks as hardforkChanges } from './hardforks' import { Chain } from './types' @@ -128,8 +129,7 @@ export default class Common { * @param hardfork Hardfork given to function as a parameter * @returns Hardfork chosen to be used */ - _chooseHardfork(hardfork?: string | null, onlySupported?: boolean): string { - onlySupported = onlySupported === undefined ? true : onlySupported + _chooseHardfork(hardfork?: string | null, onlySupported: boolean = true): string { if (!hardfork) { if (!this._hardfork) { throw new Error('Method called with neither a hardfork set nor provided by param') @@ -359,6 +359,54 @@ export default class Common { } } + /** + * Internal helper function to calculate a fork hash + * @param hardfork Hardfork name + * @returns Fork hash as hex string + */ + _calcForkHash(hardfork: string) { + const genesis = Buffer.from(this.genesis().hash.substr(2), 'hex') + + let hfBuffer = Buffer.alloc(0) + let prevBlock = 0 + for (const hf of this.hardforks()) { + const block = hf.block + + // Skip for chainstart (0), not applied HFs (null) and + // when already applied on same block number HFs + if (block !== 0 && block !== null && block !== prevBlock) { + const hfBlockBuffer = Buffer.from(block.toString(16).padStart(16, '0'), 'hex') + hfBuffer = Buffer.concat([hfBuffer, hfBlockBuffer]) + } + + if (hf.name === hardfork) break + prevBlock = block + } + const inputBuffer = Buffer.concat([genesis, hfBuffer]) + + // CRC32 delivers result as signed (negative) 32-bit integer, + // convert to hex string + const forkhash = new Number(crc32Buffer(inputBuffer) >>> 0).toString(16) + return `0x${forkhash}` + } + + /** + * Returns an eth/64 compliant fork hash (EIP-2124) + * @param hardfork Hardfork name, optional if HF set + */ + forkHash(hardfork?: string) { + hardfork = this._chooseHardfork(hardfork, false) + const data = this._getHardfork(hardfork) + if (data['block'] === null) { + const msg = 'No fork hash calculation possible for non-applied or future hardfork' + throw new Error(msg) + } + if (data['forkHash'] !== undefined) { + return data['forkHash'] + } + return this._calcForkHash(hardfork) + } + /** * Returns the Genesis parameters of current chain * @returns Genesis dictionary diff --git a/packages/common/tests/hardforks.ts b/packages/common/tests/hardforks.ts index 97f136feb8..396d307532 100644 --- a/packages/common/tests/hardforks.ts +++ b/packages/common/tests/hardforks.ts @@ -222,4 +222,56 @@ tape('[Common]: Hardfork logic', function(t: tape.Test) { st.end() }) + + t.test('_calcForkHash()', function(st: tape.Test) { + let c = new Common('mainnet') + let msg = 'should calc correctly for chainstart (only genesis)' + st.equal(c._calcForkHash('chainstart'), '0xfc64ec04', msg) + + msg = 'should calc correctly for first applied HF' + st.equal(c._calcForkHash('homestead'), '0x97c2c34c', msg) + + msg = 'should calc correctly for in-between applied HF' + st.equal(c._calcForkHash('byzantium'), '0xa00bc324', msg) + + const chains = ['mainnet', 'ropsten', 'rinkeby', 'goerli', 'kovan'] + + chains.forEach(chain => { + c = new Common(chain) + + for (const hf of c.hardforks()) { + if (hf.forkHash && hf.forkHash !== null) { + const msg = `Verify forkHash calculation for: ${chain} -> ${hf.name}` + st.equal(c._calcForkHash(hf.name), hf.forkHash, msg) + } + } + }) + + st.end() + }) + + t.test('forkHash()', function(st: tape.Test) { + let c = new Common('mainnet') + let f = () => { + c.forkHash() + } + let msg = 'should throw when no hardfork set or provided' + st.throws(f, /neither a hardfork set nor provided by param$/, msg) + + c = new Common('mainnet', 'byzantium') + msg = 'should provide correct forkHash for HF set' + st.equal(c.forkHash(), '0xa00bc324', msg) + + msg = 'should provide correct forkHash for HF provided' + st.equal(c.forkHash('spuriousDragon'), '0x3edd5b10', msg) + + c = new Common('ropsten') + f = () => { + c.forkHash('dao') + } + msg = 'should throw when called on non-applied or future HF' + st.throws(f, /No fork hash calculation possible/, msg) + + st.end() + }) })