UNPKG

incubed

Version:

Typescript-version of the incubed client

222 lines 12.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const serialize_1 = require("./serialize"); const util_1 = require("../../util/util"); const DeltaHistory_1 = require("../../util/DeltaHistory"); const merkleProof_1 = require("../../util/merkleProof"); const ethereumjs_abi_1 = require("ethereumjs-abi"); const secp256k1_1 = require("secp256k1"); const ethereumjs_util_1 = require("ethereumjs-util"); /** * verify a Blockheader and returns the percentage of finality * @param blocks * @param getChainSpec */ function checkBlockSignatures(blockHeaders, getChainSpec) { return __awaiter(this, void 0, void 0, function* () { // parse blockHeaders const blocks = blockHeaders.map(_ => _ instanceof serialize_1.Block ? _ : new serialize_1.Block(_)); // order blocks to make sure their hashes are connected for (let i = 1; i < blocks.length; i++) { if (!blocks[i - 1].hash().equals(blocks[i].parentHash)) throw new Error('The finality-Block does match the parentHash'); } // authority_round const chainSpec = yield getChainSpec(blocks[0]); const signatures = []; // we only check signatures for authorityRound if (!chainSpec || !chainSpec.spec || (chainSpec.spec.engine !== 'authorityRound' && chainSpec.spec.engine !== 'clique')) return 0; // process all blockheaders and collect their signatures yield Promise.all(blocks.map((data) => __awaiter(this, void 0, void 0, function* () { // read the current Validators const chain = data === blocks[0] ? chainSpec : yield getChainSpec(data); let signer = null; if (chainSpec.spec.engine === 'clique') { signer = getCliqueSigner(new serialize_1.Block(data.serializeHeader())); if (!chain.authorities.find(_ => signer.equals(_))) throw new Error('the author is not part of the clique authorities'); } else { // author needs to be a authority if (!chain.authorities.find(_ => data.coinbase.equals(_))) throw new Error('the author is not part of the aura authorities'); // check signature signer = getSigner(data); if (!signer || !data.coinbase.equals(signer)) throw new Error('Wrong signature for the blockheader'); } const s = signer.toString('hex'); if (signatures.indexOf(s) < 0) signatures.push(s); }))); // return the finality as percent return Math.round(signatures.length * 100 / chainSpec.authorities.length); }); } exports.checkBlockSignatures = checkBlockSignatures; function getSigner(data) { const signature = data.sealedFields[1]; const message = data.sealedFields.length === 3 ? serialize_1.hash(Buffer.concat([data.bareHash(), serialize_1.rlp.encode(data.sealedFields[2])])) : data.bareHash(); return ethereumjs_util_1.publicToAddress(secp256k1_1.recover(message, signature.slice(0, 64), signature[64]), true); } exports.getSigner = getSigner; function getCliqueSigner(data) { const sig = data.extra.slice(data.extra.length - 65, data.extra.length); data.raw[12] = data.extra.slice(0, data.extra.length - 65); return ethereumjs_util_1.publicToAddress(secp256k1_1.recover(data.hash(), sig.slice(0, 64), sig[64]), true); } function addCliqueValidators(history, ctx, states) { const epoch = ctx.chainSpec.epoch || 30000; for (const s of states) { const current = [...history.getData(s.block)]; if (JSON.stringify(current) === JSON.stringify(s.validators)) continue; const add = s.validators.length > current.length; const ep = Math.floor(s.block / epoch); let proofCount = 0; let newValidator = null; let verified = false; for (const p of s.proof) { const block = new serialize_1.Block(p); const signer = '0x' + getCliqueSigner(block).toString('hex'); if (current.indexOf(signer) < 0) continue; // this is no proof! if (block.sealedFields[1].toString('hex') !== (add ? 'ffffffffffffffff' : '0000000000000000')) continue; // wrong proof if (Math.floor(util_1.toNumber(block.number) / epoch) != ep) continue; // wrong epoch if (block.coinbase.toString('hex') == '0000000000000000000000000000000000000000') continue; // wrong validator if (!newValidator) newValidator = block.coinbase; else if (!newValidator.equals(block.coinbase)) continue; // wrong validator proofCount++; if (proofCount == Math.floor(current.length / 2) + 1) { const nv = '0x' + newValidator.toString('hex'); verified = add ? (current.indexOf(nv) < 0 && s.validators.indexOf(nv) >= 0) : (current.indexOf(nv) >= 0 && s.validators.indexOf(nv) < 0); break; } } if (verified) history.addState(s.block, s.validators); } } function addAuraValidators(history, ctx, states) { return __awaiter(this, void 0, void 0, function* () { for (const s of states) { //skip the current block if already added in the delta const current = history.getData(s.block).map(h => serialize_1.address(h.startsWith('0x') ? h : '0x' + h)); if (Buffer.concat(current).equals(Buffer.concat(s.validators.map(serialize_1.address)))) continue; const proof = s.proof; if (!s.proof) throw new Error('The validator list has no proof'); // decode the blockheader const block = serialize_1.blockFromHex(proof.block); const finalitySigners = []; //verify blockheaders if (util_1.toNumber(s.block) !== util_1.toNumber(block.number)) throw new Error("Block Number in validator Proof doesn't match"); let parentHash = block.parentHash; for (const b of [block, ...(proof.finalityBlocks || []).map(serialize_1.blockFromHex)]) { if (!parentHash.equals(b.parentHash)) throw new Error('Invalid ParentHash'); const signer = getSigner(b); const proposer = current[b.sealedFields[0].readUInt32BE(0) % current.length]; if (!Buffer.concat(current).includes(signer)) throw new Error('Block was signed by the wrong validator'); if (!finalitySigners.find(_ => _.equals(signer))) finalitySigners.push(signer); parentHash = b.hash(); } //get the required finality from the default config of the client const reqFinality = (ctx.client && ctx.client.defConfig && ctx.client.defConfig.finality) || 0; //check if the finality of the response is greater than or equal to the required finality if (Math.ceil(reqFinality * current.length / 100) > finalitySigners.length) throw new Error('Not enough finality to accept the state (' + finalitySigners.length + '/' + (Math.ceil(reqFinality * current.length / 100)) + ')'); // now check the receipt const receipt = serialize_1.rlp.decode(yield merkleProof_1.default(block.receiptTrie, // expected merkle root serialize_1.rlp.encode(proof.txIndex), // path, which is the transsactionIndex proof.proof.map(serialize_1.bytes), // array of Buffer with the merkle-proof-data undefined // we don't want to check, but use the found value in the next step )); const logData = receipt[receipt.length - 1][proof.logIndex]; if (!logData) throw new Error('Validator changeLog not found in Transaction'); //check for contract address from chain spec if (!logData[0].equals(serialize_1.address(ctx.chainSpec.validatorContract))) throw new Error('Wrong address in log '); //check for the standard topic "0x55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c89" if (!logData[1][0].equals(serialize_1.bytes32('0x55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c89'))) throw new Error('Wrong Topics in log '); //check the list if (!logData[2].equals(serialize_1.bytes(ethereumjs_abi_1.rawEncode(['address[]'], [s.validators.map(v => v.startsWith('0x') ? v : ('0x' + v))])))) throw new Error('Wrong data in log '); history.addState(s.block, s.validators); } }); } function checkForValidators(ctx, validators) { return __awaiter(this, void 0, void 0, function* () { if (ctx.chainSpec.engine == 'clique') { const list = yield ctx.client.sendRPC('in3_validatorlist', [validators.data.length, null], ctx.chainId, { proof: 'none' }); addCliqueValidators(validators, ctx, list.result && list.result.states); } else if (ctx.chainSpec.engine == 'authorityRound') { const list = yield ctx.client.sendRPC('in3_validatorlist', [validators.data.length, null], ctx.chainId, { proof: 'none' }); yield addAuraValidators(validators, ctx, list.result && list.result.states); } ctx.putInCache('validators', JSON.stringify(validators.toDeltaStrings())); }); } function getChainSpec(b, ctx) { return __awaiter(this, void 0, void 0, function* () { //handle POS chains with defined validator list if (ctx.chainSpec.engine == 'authorityRound' && ctx.chainSpec.validatorList && !ctx.chainSpec.validatorContract) { const res = { authorities: ctx.chainSpec.validatorList.map(h => serialize_1.address(h.startsWith('0x') ? h : '0x' + h)), spec: ctx.chainSpec, }; // for now we do n ot specify a proposer which mean anyone of the validators could sign. // res.proposer = address(ctx.chainSpec.validatorList[b.sealedFields[0].readUInt32BE(0) % res.authorities.length]) return res; } let validators = null; const cache = ctx.getFromCache('validators'); if (cache) { try { validators = new DeltaHistory_1.default(JSON.parse(cache), true); } catch (x) { } } // no validators in the cache yet, so we have to find them. if (!validators) validators = new DeltaHistory_1.default(ctx.chainSpec.validatorList, false); const lastKnownValidatorChange = validators.getLastIndex(); //if there is an update in the validator list then get it if (ctx.lastValidatorChange > lastKnownValidatorChange) yield checkForValidators(ctx, validators); // get the current validator-list for the block const res = { authorities: validators.getData(util_1.toNumber(b.number)).map(h => serialize_1.address(h.startsWith('0x') ? h : '0x' + h)), spec: ctx.chainSpec }; // find out who is able to sign with this nonce res.proposer = res.authorities[(ctx.chainSpec.engine == 'clique' ? util_1.toNumber(b.number) : b.sealedFields[0].readUInt32BE(0)) % res.authorities.length]; if (ctx.chainSpec.engine == 'clique' && util_1.toNumber(b.difficulty) === 1) res.proposer = null; return res; }); } exports.getChainSpec = getChainSpec; //# sourceMappingURL=header.js.map