incubed
Version:
Typescript-version of the incubed client
222 lines • 12.4 kB
JavaScript
;
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