all: simple checkpoint syncing#17578
Conversation
6d90140 to
f527438
Compare
f46c084 to
e2c55c5
Compare
3202109 to
f65abbf
Compare
723efbb to
08ac22c
Compare
c8df948 to
a671842
Compare
a671842 to
d221fd1
Compare
4cc7cb0 to
4500873
Compare
a49dab2 to
32337ba
Compare
8927861 to
1ea1da5
Compare
|
@zsfelfoldi Btw I have tested the latest PR by connecting the client and server and running a light syncing based on the checkpoint registered in the contract, it works. |
|
@zsfelfoldi I remove the annoying callback stuff and move the backend setting logic after the node start. Hope it makes sense to you :)) |
7a89d88 to
f3c835f
Compare
zsfelfoldi
left a comment
There was a problem hiding this comment.
PR is approved, please resolve merge conflicts and fix the linter warnings:
cmd/registrar/common.go:36:2:warning: unused variable or constant errInvalidURL (varcheck)
cmd/registrar/common.go:37:2:warning: unused variable or constant errNoSpecifiedAddr (varcheck)
Note: the registrar contract seems fine to me but we need a more careful security examination before deploying it on the mainnet. In the meantime we can test it on Rinkeby.
7ce4ce6 to
1f3ae3b
Compare
768bf32 to
c796bc3
Compare
There was a problem hiding this comment.
So, we've been thinking that there's no real reason (correct us if wrong) to maintain a set of old checkpoints. We only ever care about the latest. That restriction would probably reduce the contract complexity a bit.
There was a problem hiding this comment.
Wondering if this is worth the complexity.
There was a problem hiding this comment.
We had this idea for reorg protection: When voting, also pass in the current block number and hash (or maybe one from 10 blocks). That way if there's a reorg, the votes will fail on that "chain" and we don't serve up bad CHTs, rather revert to an older set one.
There was a problem hiding this comment.
Swap so guy with the F is happy :D
cmd, les, node: remove callback mechanism cmd, node: remove callback definition les: simplify the registrar les: expose checkpoint rpc services in the light client les, light: don't store untrusted receipt
c796bc3 to
8cd7f4f
Compare
8cd7f4f to
1445145
Compare
|
Please take a look at https://eips.ethereum.org/EIPS/eip-191 , see if we can use that prefix scheme for the signatures |
|
Would it be possible to simplify the whole thing, contract-wise, with some variation of this: // SetCheckpoint sets a new checkpoint. It accepts a list of signatures
// @_recentNumber: a recent blocknumber, for replay protection
// @_recentHash : the hash of `_recentNumber`
// @_hash : the hash to set at _sectionIndex
// @_sectionIndex : the section index to set
// @v : the list of v-values
// @r : the list or r-values
// @s : the list of s-values
function SetCheckpoint(uint _recentNumber,
bytes32 _recentHash,
bytes32 _hash,
uint _sectionIndex ,
uint8 []v, bytes32 []r, bytes32 []s)
public
returns (bytes32 transactionHash)
{
// This checks replay protection, so it cannot be replayed on forks,
// accidentally or intentionally
require(admins[msg.sender]);
require(blockhash(_recentNumber) == _recentHash);
assert(v.length == r.length)
assert(v.length == s.length)
// Filter out "future" checkpoint.
if (block.number < (_sectionIndex+1)*sectionSize+processConfirms) {
return false;
}
// Filter out "old" announcement
if (_sectionIndex < sectionIndex) {
return false;
}
// Filter out "stale" announcement
if (_sectionIndex == sectionIndex && (_sectionIndex != 0 || height != 0)) {
return false;
}
// Filter out "invalid" announcement
if (_hash == ""){
return false
}
// EIP 191 style signatures
// Arguments when calculating hash to validate
// 1: byte(0x19) - the initial 0x19 byte
// 2: byte(0) - the version byte (data with intended validator)
// 3: this - the validator address
// 4,5 : Application specific data
signedHash = keccak256(byte(0x19),byte(0),this, _hash, _sectionIndex);
// Tally up the votes
int memory votes = 0
mapping(address => bool) memory voted
for (uint idx = 0; idx < v.length; idx++){
var signer = ecrecover(signedHash, v[idx], r[idx], s[idx]);
if admins[signer] && !voted[signer]{
votes ++
if votes >= threshold{
// Sufficient signatures present
// Update latest checkpoint
hash = _hash;
height = block.number;
sectionIndex = _sectionIndex;
// TODO! Build the 'sigs' struct
emit NewCheckpointEvent(_sectionIndex, _hash, sigs);
}
}
}
}The code above has the following differences:
If we decide that we don't need the replay protection, we can even make the method totally public, so it doesn't even require admins to call it -- only to sign a eip-191 data structure and send to the submitter. |
|
To clarify, the EIP 191 version |
|
That would give us a concrete way to try out the UI of clef. I imagine the UX should be something like |
|
(though, miight be nicer to use |
|
Here's a more complete version, which I think is a lot simpler than the one in this PR. pragma solidity ^0.5.2;
/**
* @title Registrar
* @author Gary Rong<garyrong@ethereum.org>, Martin Swende <martin.swende@ethereum.org>
* @dev Implementation of the blockchain checkpoint registrar.
*/
contract Registrar {
/*
Fields
*/
// A map of admin users who have the permission to update CHT and bloom Trie root
mapping(address => bool) admins;
// Latest stored section id
uint sectionIndex;
// The block height associated with latest registered checkpoint.
uint height;
// The hash of latest registered checkpoint.
bytes32 hash;
// The frequency for creating a checkpoint
//
// The default value should be the same as the checkpoint size(32768) in the ethereum.
uint8 sectionSize;
// The number of confirmations needed before a checkpoint can be registered.
// We have to make sure the checkpoint registered will not be invalid due to
// chain reorg.
//
// The default value should be the same as the checkpoint process confirmations(256)
// in the ethereum.
uint processConfirms;
// The required signatures to finalize a stable checkpoint.
uint8 threshold;
/*
Events
*/
// NewCheckpointVote is emitted when a new checkpoint proposal receives a vote.
event NewCheckpointVote(uint8 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s);
/*
Public Functions
*/
constructor(address[] memory _adminlist, uint8 _sectionSize, uint _processConfirms, uint8 _threshold) public {
for (uint i = 0; i < _adminlist.length; i++) {
admins[_adminlist[i]] = true;
}
sectionSize = _sectionSize;
processConfirms = _processConfirms;
threshold = _threshold;
}
// SetCheckpoint sets a new checkpoint. It accepts a list of signatures
// @_recentNumber: a recent blocknumber, for replay protection
// @_recentHash : the hash of `_recentNumber`
// @_hash : the hash to set at _sectionIndex
// @_sectionIndex : the section index to set
// @v : the list of v-values
// @r : the list or r-values
// @s : the list of s-values
function SetCheckpoint(uint _recentNumber,
bytes32 _recentHash,
bytes32 _hash,
uint8 _sectionIndex ,
uint8[] memory v,
bytes32[] memory r,
bytes32[] memory s)
public
returns (bool)
{
// These checks replay protection, so it cannot be replayed on forks,
// accidentally or intentionally
require(admins[msg.sender]);
require(blockhash(_recentNumber) == _recentHash);
assert(v.length == r.length);
assert(v.length == s.length);
// Filter out "future" checkpoint.
if (block.number < (_sectionIndex+1)*sectionSize+processConfirms) {
return false;
}
// Filter out "old" announcement
if (_sectionIndex < sectionIndex) {
return false;
}
// Filter out "stale" announcement
if (_sectionIndex == sectionIndex && (_sectionIndex != 0 || height != 0)) {
return false;
}
// Filter out "invalid" announcement
if (_hash == ""){
return false;
}
// EIP 191 style signatures
//
// Arguments when calculating hash to validate
// 1: byte(0x19) - the initial 0x19 byte
// 2: byte(0) - the version byte (data with intended validator)
// 3: this - the validator address
// -- Application specific data
// 4 : section index (uint8)
// 5 : hash (bytes32)
bytes32 signedHash = keccak256(abi.encodePacked(byte(0x19),byte(0),this,_sectionIndex, _hash ));
// Tally up the votes
address lastVoter = address(0);
// In order for us not to have to maintain a mapping
// of who has already voted, and we don't want to count a
// vote twice, the signatures must be submitted in
// strict ordering
for (uint idx = 0; idx < v.length; idx++){
address signer = ecrecover(signedHash, v[idx], r[idx], s[idx]);
require( admins[signer] );
require( uint256(signer) >= uint256(lastVoter));
lastVoter = signer;
emit NewCheckpointVote(_sectionIndex, _hash, v[idx], r[idx], s[idx]);
if (idx+1 >= threshold){
// Sufficient signatures present
// Update latest checkpoint
hash = _hash;
height = block.number;
sectionIndex = _sectionIndex;
return true;
}
}
// We shouldn't wind up here, reverting un-emits the events
revert();
}
}A couple of things to note:
|
In this PR, we introduce a more flexible approach to update the checkpoint to fasten light client syncing instead of relying on hardcoded one.
Basially the idea is we can deploy a smart contract on the blockchain and put all stable checkpoints in there. Light client can verify the correntness of advertised checkpoint which announced by remote peer through the contract.
For the security consideration, we rely on the elliptic curve cryptography.
Pre-requisites
the code of contract should looks like:
Besides, we provide a registrar help tool to help operator to register checkpoint correctly.
So all in all, this action just like updating hard code checkpoint before release by core developers, but it is definitely more flexible and same secure as the hardcoded one.
Simple checkpoint syncing
Server side
Note the persisted checkpoint should containes two things:
Light client side
Admin side
Security model
For the simple checkpoint syncing, the security is guaranteed by ECDSA.
Weakness
TODO