cmd, contracts: add replay-protection

This commit is contained in:
rjl493456442 2019-04-25 20:59:23 +08:00
parent 5d31d9f277
commit 144514518c
7 changed files with 95 additions and 41 deletions

View file

@ -21,6 +21,7 @@ import (
"math/big"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/cmd/utils"
@ -102,7 +103,7 @@ func registerCheckpoint(ctx *cli.Context) error {
var result [3]string
index := ctx.GlobalInt64(checkpointIndexFlag.Name)
if err := rpcClient.Call(&result, "les_getCheckpoint", index); err != nil {
utils.Fatalf("Failed to get local checkpoint %v", err)
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
}
checkpoint = &params.TrustedCheckpoint{
SectionIndex: uint64(index),
@ -116,7 +117,7 @@ func registerCheckpoint(ctx *cli.Context) error {
var result [4]string
err := rpcClient.Call(&result, "les_latestCheckpoint")
if err != nil {
utils.Fatalf("Failed to get local checkpoint %v", err)
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
}
index, err := strconv.ParseUint(result[0], 0, 64)
if err != nil {
@ -132,12 +133,15 @@ func registerCheckpoint(ctx *cli.Context) error {
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot, "hash", checkpoint.Hash())
}
contract := setupContract(rpcClient)
// Filter out stale checkpoint announcement
// Ensure the validness of the checkpoint.
latest, _, h, err := contract.Contract().GetLatestCheckpoint(nil)
if err != nil {
return err
}
head, err := ethclient.NewClient(rpcClient).HeaderByNumber(context.Background(), nil)
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
head, err := ethclient.NewClient(rpcClient).HeaderByNumber(reqCtx, nil)
if err != nil {
return err
}
@ -151,7 +155,8 @@ func registerCheckpoint(ctx *cli.Context) error {
if checkpoint.SectionIndex == latest.Uint64() && (latest.Uint64() != 0 || h.Uint64() != 0) {
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest.Uint64(), checkpoint.SectionIndex)
}
// Ensure the invoker is a trusted signer.
// Ensure the caller is a trusted signer.
signers, err := contract.Contract().GetAllAdmin(nil)
if err != nil {
return err
@ -166,8 +171,15 @@ func registerCheckpoint(ctx *cli.Context) error {
if !trusted {
utils.Fatalf("Address %s is not a trusted signer", key.Address)
}
// Fetch identity information
identity, err := ethclient.NewClient(rpcClient).HeaderByNumber(reqCtx, big.NewInt(int64(headNumber-128)))
if err != nil {
return err
}
// Register the checkpoint
tx, err := contract.SetCheckpoint(key.PrivateKey, big.NewInt(int64(checkpoint.SectionIndex)), checkpoint.Hash().Bytes())
tx, err := contract.SetCheckpoint(key.PrivateKey, big.NewInt(int64(checkpoint.SectionIndex)), checkpoint.Hash().Bytes(), identity.Number.Uint64(), identity.Hash())
if err != nil {
return err
}

File diff suppressed because one or more lines are too long

View file

@ -37,6 +37,14 @@ contract Registrar {
_;
}
/**
* @dev Check whether the vote truly belongs to local chain.
*/
modifier replayProtection(uint64 _number, bytes32 _hash) {
require(blockhash(_number) == _hash);
_;
}
/*
Events
*/
@ -81,6 +89,8 @@ contract Registrar {
*
* @param _sectionIndex section index
* @param _hash checkpoint hash calculated in the client side
* @param _identityNumber block number used to protect transaction replay.
* @param _identityHash corresponding block hash used to protect transaction replay.
* @param _sig admin's signature for checkpoint hash
* `checkpoint_hash = Hash(index, sectionHead, chtRoot, bloomRoot)`
* `_sig = Sign(privateKey, checkpoint_hash)`
@ -89,8 +99,11 @@ contract Registrar {
function SetCheckpoint(
uint _sectionIndex,
bytes32 _hash,
uint64 _identityNumber,
bytes32 _identityHash,
bytes memory _sig
)
replayProtection(_identityNumber, _identityHash)
OnlyAuthorized
public
returns(bool)
@ -234,7 +247,6 @@ contract Registrar {
address[] adminList;
// Latest stored section id
// Note all registered checkpoint information should continuous with previous one.
uint sectionIndex;
// The block height associated with latest registered checkpoint.

View file

@ -66,12 +66,12 @@ func (registrar *Registrar) LookupCheckpointEvent(blockLogs [][]*types.Log, sect
}
// SetCheckpoint creates a signature for given checkpoint with specified private key and registers into contract.
func (registrar *Registrar) SetCheckpoint(key *ecdsa.PrivateKey, sectionIndex *big.Int, hash []byte) (*types.Transaction, error) {
func (registrar *Registrar) SetCheckpoint(key *ecdsa.PrivateKey, sectionIndex *big.Int, hash []byte, identityNumber uint64, identityHash [32]byte) (*types.Transaction, error) {
sig, err := crypto.Sign(hash, key)
if err != nil {
return nil, err
}
var h [32]byte
copy(h[:], hash)
return registrar.contract.SetCheckpoint(bind.NewKeyedTransactor(key), sectionIndex, h, sig)
return registrar.contract.SetCheckpoint(bind.NewKeyedTransactor(key), sectionIndex, h, identityNumber, identityHash, sig)
}

View file

@ -174,9 +174,26 @@ func TestCheckpointRegister(t *testing.T) {
}
contractBackend.Commit()
// getIdentity returns block height and hash of the head parent.
getIdentity := func() (uint64, common.Hash) {
parentNumber := contractBackend.Blockchain().CurrentHeader().Number.Uint64() - 1
parentHash := contractBackend.Blockchain().CurrentHeader().ParentHash
return parentNumber, parentHash
}
// Register unstable checkpoint
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil)
}, "register unstable checkpoint")
// Test transaction replay
validateOperation(t, c, contractBackend, func() {
number, hash := getIdentity()
hash = common.HexToHash("deadbeef")
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil)
}, "register unstable checkpoint")
@ -185,51 +202,58 @@ func TestCheckpointRegister(t *testing.T) {
// Register by unauthorized user
validateOperation(t, c, contractBackend, func() {
number, hash := getIdentity()
u, _ := crypto.GenerateKey()
unauthorized := bind.NewKeyedTransactor(u)
c.SetCheckpoint(unauthorized, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(u, checkpoint0.Hash()))
c.SetCheckpoint(unauthorized, big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(u, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil)
}, "register by unauthorized user")
// Submit a new checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()})
}, "single checkpoint announcement")
// Submit a duplicate checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()})
}, "duplicate checkpoint announcement")
// Modification
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), common.HexToHash("deadbeef"), signCheckpoint(accounts[0].key, common.HexToHash("deadbeef")))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(0), common.HexToHash("deadbeef"), number, hash, signCheckpoint(accounts[0].key, common.HexToHash("deadbeef")))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef")})
}, "checkpoint modification")
// Modification
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), common.HexToHash("deadbeef2"), signCheckpoint(accounts[0].key, common.HexToHash("deadbeef2")))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(0), common.HexToHash("deadbeef2"), number, hash, signCheckpoint(accounts[0].key, common.HexToHash("deadbeef2")))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef2")})
}, "checkpoint modification")
// Another correct checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[1].key, checkpoint0.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[1].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr, accounts[1].addr}, []common.Hash{common.HexToHash("deadbeef2"), checkpoint0.Hash()})
}, "another checkpoint announcement")
// enough checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[2].key), big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[2].key, checkpoint0.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[2].key), big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[2].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
if valid, recv := validateEvents(1, events); !valid {
return errors.New("receive incorrect number of events")
@ -249,14 +273,16 @@ func TestCheckpointRegister(t *testing.T) {
// submit a stale checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint0.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil)
}, "submit stale checkpoint announcement")
// submit a future checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(1), checkpoint1.Hash(), signCheckpoint(accounts[0].key, checkpoint1.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(1), checkpoint1.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint1.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil)
}, "submit future checkpoint announcement")
@ -265,8 +291,9 @@ func TestCheckpointRegister(t *testing.T) {
distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64()
contractBackend.InsertEmptyBlocks(int(distance))
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[0].key, checkpoint2.Hash()))
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[1].key, checkpoint2.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(2), checkpoint2.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint2.Hash()))
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(2), checkpoint2.Hash(), number, hash, signCheckpoint(accounts[1].key, checkpoint2.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
if valid, recv := validateEvents(1, events); !valid {
return errors.New("receive incorrect number of events")
@ -286,7 +313,8 @@ func TestCheckpointRegister(t *testing.T) {
// submit a stale checkpoint announcement
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[0].key, checkpoint2.Hash()))
number, hash := getIdentity()
c.SetCheckpoint(transactOpts, big.NewInt(2), checkpoint2.Hash(), number, hash, signCheckpoint(accounts[0].key, checkpoint2.Hash()))
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil)
}, "submit stale checkpoint announcement")

View file

@ -202,10 +202,10 @@ func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []
}
var reg *checkpointRegistrar
if indexers != nil {
getLocal := func(index uint64) light.TrustedCheckpoint {
getLocal := func(index uint64) params.TrustedCheckpoint {
chtIndexer := indexers[0]
sectionHead := chtIndexer.SectionHead(index)
return light.TrustedCheckpoint{
return params.TrustedCheckpoint{
SectionIndex: index,
SectionHead: sectionHead,
CHTRoot: light.GetChtRoot(db, index, sectionHead),

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/params"
)
func TestCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2) }
@ -49,13 +50,14 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
chtRoot := light.GetChtRoot(server.db, 7, head)
btRoot := light.GetBloomTrieRoot(server.db, bts-1, head)
cp := &light.TrustedCheckpoint{
cp := &params.TrustedCheckpoint{
SectionIndex: 0,
SectionHead: head,
CHTRoot: chtRoot,
BloomRoot: btRoot,
}
if _, err := server.pm.reg.contract.SetCheckpoint(signerKey, big.NewInt(int64(cp.SectionIndex)), cp.Hash().Bytes()); err != nil {
header := server.backend.Blockchain().CurrentHeader()
if _, err := server.pm.reg.contract.SetCheckpoint(signerKey, big.NewInt(int64(cp.SectionIndex)), cp.Hash().Bytes(), header.Number.Uint64(), header.Hash()); err != nil {
t.Error("register checkpoint failed", err)
}
server.backend.Commit()
@ -63,7 +65,7 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
// Wait for the checkpoint registration
for {
hash, _, err := server.pm.reg.contract.Contract().GetCheckpoint(nil, big.NewInt(0))
_, hash, _, err := server.pm.reg.contract.Contract().GetLatestCheckpoint(nil)
if err != nil || hash == [32]byte{} {
time.Sleep(100 * time.Millisecond)
continue