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" "math/big"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
@ -102,7 +103,7 @@ func registerCheckpoint(ctx *cli.Context) error {
var result [3]string var result [3]string
index := ctx.GlobalInt64(checkpointIndexFlag.Name) index := ctx.GlobalInt64(checkpointIndexFlag.Name)
if err := rpcClient.Call(&result, "les_getCheckpoint", index); err != nil { 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{ checkpoint = &params.TrustedCheckpoint{
SectionIndex: uint64(index), SectionIndex: uint64(index),
@ -116,7 +117,7 @@ func registerCheckpoint(ctx *cli.Context) error {
var result [4]string var result [4]string
err := rpcClient.Call(&result, "les_latestCheckpoint") err := rpcClient.Call(&result, "les_latestCheckpoint")
if err != nil { 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) index, err := strconv.ParseUint(result[0], 0, 64)
if err != nil { if err != nil {
@ -132,12 +133,15 @@ func registerCheckpoint(ctx *cli.Context) error {
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot, "hash", checkpoint.Hash()) "chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot, "hash", checkpoint.Hash())
} }
contract := setupContract(rpcClient) contract := setupContract(rpcClient)
// Filter out stale checkpoint announcement
// Ensure the validness of the checkpoint.
latest, _, h, err := contract.Contract().GetLatestCheckpoint(nil) latest, _, h, err := contract.Contract().GetLatestCheckpoint(nil)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -151,7 +155,8 @@ func registerCheckpoint(ctx *cli.Context) error {
if checkpoint.SectionIndex == latest.Uint64() && (latest.Uint64() != 0 || h.Uint64() != 0) { if checkpoint.SectionIndex == latest.Uint64() && (latest.Uint64() != 0 || h.Uint64() != 0) {
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest.Uint64(), checkpoint.SectionIndex) 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) signers, err := contract.Contract().GetAllAdmin(nil)
if err != nil { if err != nil {
return err return err
@ -166,8 +171,15 @@ func registerCheckpoint(ctx *cli.Context) error {
if !trusted { if !trusted {
utils.Fatalf("Address %s is not a trusted signer", key.Address) 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 // 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 { if err != nil {
return err 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 Events
*/ */
@ -81,6 +89,8 @@ contract Registrar {
* *
* @param _sectionIndex section index * @param _sectionIndex section index
* @param _hash checkpoint hash calculated in the client side * @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 * @param _sig admin's signature for checkpoint hash
* `checkpoint_hash = Hash(index, sectionHead, chtRoot, bloomRoot)` * `checkpoint_hash = Hash(index, sectionHead, chtRoot, bloomRoot)`
* `_sig = Sign(privateKey, checkpoint_hash)` * `_sig = Sign(privateKey, checkpoint_hash)`
@ -89,8 +99,11 @@ contract Registrar {
function SetCheckpoint( function SetCheckpoint(
uint _sectionIndex, uint _sectionIndex,
bytes32 _hash, bytes32 _hash,
uint64 _identityNumber,
bytes32 _identityHash,
bytes memory _sig bytes memory _sig
) )
replayProtection(_identityNumber, _identityHash)
OnlyAuthorized OnlyAuthorized
public public
returns(bool) returns(bool)
@ -234,7 +247,6 @@ contract Registrar {
address[] adminList; address[] adminList;
// Latest stored section id // Latest stored section id
// Note all registered checkpoint information should continuous with previous one.
uint sectionIndex; uint sectionIndex;
// The block height associated with latest registered checkpoint. // 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. // 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) sig, err := crypto.Sign(hash, key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var h [32]byte var h [32]byte
copy(h[:], hash) 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() 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 // Register unstable checkpoint
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil) return assert(c, 0, nil, nil)
}, "register unstable checkpoint") }, "register unstable checkpoint")
@ -185,51 +202,58 @@ func TestCheckpointRegister(t *testing.T) {
// Register by unauthorized user // Register by unauthorized user
validateOperation(t, c, contractBackend, func() { validateOperation(t, c, contractBackend, func() {
number, hash := getIdentity()
u, _ := crypto.GenerateKey() u, _ := crypto.GenerateKey()
unauthorized := bind.NewKeyedTransactor(u) 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil) return assert(c, 0, nil, nil)
}, "register by unauthorized user") }, "register by unauthorized user")
// Submit a new checkpoint announcement // Submit a new checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()}) return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()})
}, "single checkpoint announcement") }, "single checkpoint announcement")
// Submit a duplicate checkpoint announcement // Submit a duplicate checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()}) return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()})
}, "duplicate checkpoint announcement") }, "duplicate checkpoint announcement")
// Modification // Modification
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef")}) return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef")})
}, "checkpoint modification") }, "checkpoint modification")
// Modification // Modification
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef2")}) return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef2")})
}, "checkpoint modification") }, "checkpoint modification")
// Another correct checkpoint announcement // Another correct checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, 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()}) return assert(c, 0, []common.Address{accounts[0].addr, accounts[1].addr}, []common.Hash{common.HexToHash("deadbeef2"), checkpoint0.Hash()})
}, "another checkpoint announcement") }, "another checkpoint announcement")
// enough checkpoint announcement // enough checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
if valid, recv := validateEvents(1, events); !valid { if valid, recv := validateEvents(1, events); !valid {
return errors.New("receive incorrect number of events") return errors.New("receive incorrect number of events")
@ -249,14 +273,16 @@ func TestCheckpointRegister(t *testing.T) {
// submit a stale checkpoint announcement // submit a stale checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil) return assert(c, 0, nil, nil)
}, "submit stale checkpoint announcement") }, "submit stale checkpoint announcement")
// submit a future checkpoint announcement // submit a future checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil) return assert(c, 0, nil, nil)
}, "submit future checkpoint announcement") }, "submit future checkpoint announcement")
@ -265,8 +291,9 @@ func TestCheckpointRegister(t *testing.T) {
distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64() distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64()
contractBackend.InsertEmptyBlocks(int(distance)) contractBackend.InsertEmptyBlocks(int(distance))
validateOperation(t, c, contractBackend, func() { validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[0].key, checkpoint2.Hash())) number, hash := getIdentity()
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[1].key, checkpoint2.Hash())) 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
if valid, recv := validateEvents(1, events); !valid { if valid, recv := validateEvents(1, events); !valid {
return errors.New("receive incorrect number of events") return errors.New("receive incorrect number of events")
@ -286,7 +313,8 @@ func TestCheckpointRegister(t *testing.T) {
// submit a stale checkpoint announcement // submit a stale checkpoint announcement
validateOperation(t, c, contractBackend, func() { 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 { }, func(events <-chan *contract.ContractNewCheckpointEvent) error {
return assert(c, 0, nil, nil) return assert(c, 0, nil, nil)
}, "submit stale checkpoint announcement") }, "submit stale checkpoint announcement")

View file

@ -202,10 +202,10 @@ func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []
} }
var reg *checkpointRegistrar var reg *checkpointRegistrar
if indexers != nil { if indexers != nil {
getLocal := func(index uint64) light.TrustedCheckpoint { getLocal := func(index uint64) params.TrustedCheckpoint {
chtIndexer := indexers[0] chtIndexer := indexers[0]
sectionHead := chtIndexer.SectionHead(index) sectionHead := chtIndexer.SectionHead(index)
return light.TrustedCheckpoint{ return params.TrustedCheckpoint{
SectionIndex: index, SectionIndex: index,
SectionHead: sectionHead, SectionHead: sectionHead,
CHTRoot: light.GetChtRoot(db, index, 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/core"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/params"
) )
func TestCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2) } 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) chtRoot := light.GetChtRoot(server.db, 7, head)
btRoot := light.GetBloomTrieRoot(server.db, bts-1, head) btRoot := light.GetBloomTrieRoot(server.db, bts-1, head)
cp := &light.TrustedCheckpoint{ cp := &params.TrustedCheckpoint{
SectionIndex: 0, SectionIndex: 0,
SectionHead: head, SectionHead: head,
CHTRoot: chtRoot, CHTRoot: chtRoot,
BloomRoot: btRoot, 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) t.Error("register checkpoint failed", err)
} }
server.backend.Commit() server.backend.Commit()
@ -63,7 +65,7 @@ func testCheckpointSyncing(t *testing.T, protocol int) {
// Wait for the checkpoint registration // Wait for the checkpoint registration
for { 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{} { if err != nil || hash == [32]byte{} {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
continue continue