mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
cmd, contracts: add replay-protection
This commit is contained in:
parent
5d31d9f277
commit
144514518c
7 changed files with 95 additions and 41 deletions
|
|
@ -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 = ¶ms.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
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 := ¶ms.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
|
||||
|
|
|
|||
Loading…
Reference in a new issue