clique, signer: implement clique signing for clef

This commit is contained in:
Martin Holst Swende 2018-09-25 11:07:18 +02:00
parent 8e0bc14235
commit 9e3f27fc18
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
8 changed files with 69 additions and 45 deletions

View file

@ -39,7 +39,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
lru "github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
) )
const ( const (
@ -136,9 +136,9 @@ var (
errRecentlySigned = errors.New("recently signed") errRecentlySigned = errors.New("recently signed")
) )
// SignerFn is a signer callback function to request a hash to be signed by a // SignerFn is a signer callback function to request a block header to be signed by a
// backing account. // backing account.
type SignerFn func(accounts.Account, []byte) ([]byte, error) type SignerFn func(accounts.Account, *types.Header) ([]byte, error)
// sigHash returns the hash which is used as input for the proof-of-authority // sigHash returns the hash which is used as input for the proof-of-authority
// signing. It is the hash of the entire header apart from the 65 byte signature // signing. It is the hash of the entire header apart from the 65 byte signature
@ -646,7 +646,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
} }
// Sign all the things! // Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) sighash, err := signFn(accounts.Account{Address: signer}, header)
if err != nil { if err != nil {
return err return err
} }

View file

@ -219,7 +219,7 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
} }
} }
func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerAPI { func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerClient {
return b.eth.externalSigner return b.eth.externalSigner
} }

View file

@ -20,6 +20,7 @@ package eth
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/accounts"
"math/big" "math/big"
"runtime" "runtime"
"sync" "sync"
@ -76,7 +77,7 @@ type Ethereum struct {
eventMux *event.TypeMux eventMux *event.TypeMux
engine consensus.Engine engine consensus.Engine
externalSigner *ethapi.ExternalSignerAPI externalSigner *ethapi.ExternalSignerClient
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
@ -100,7 +101,7 @@ func (s *Ethereum) AddLesServer(ls LesServer) {
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerAPI) (*Ethereum, error) { func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerClient) (*Ethereum, error) {
// Ensure configuration values are compatible and sane // Ensure configuration values are compatible and sane
if config.SyncMode == downloader.LightSync { if config.SyncMode == downloader.LightSync {
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
@ -413,10 +414,19 @@ func (s *Ethereum) StartMining(threads int) error {
log.Error("Cannot start mining without etherbase", "err", err) log.Error("Cannot start mining without etherbase", "err", err)
return fmt.Errorf("etherbase missing: %v", err) return fmt.Errorf("etherbase missing: %v", err)
} }
if _, ok := s.engine.(*clique.Clique); ok { if clique, ok := s.engine.(*clique.Clique); ok {
//TODO @holiman if s.ExternalSigner() != nil {
log.Error("Etherbase account unavailable locally", "err", err) signerFn := func(a accounts.Account, header *types.Header) ([]byte, error) {
return fmt.Errorf("signer not configured: %v", err) rlpHeader, err := rlp.EncodeToBytes(header)
if err != nil {
return nil, err
}
return s.ExternalSigner().SignCliqueBlock(a.Address, rlpHeader)
}
clique.Authorize(eb, signerFn)
} else {
return fmt.Errorf("external signer not configured")
}
} }
// If mining is started, we can disable the transaction rejection mechanism // If mining is started, we can disable the transaction rejection mechanism
// introduced to speed sync times. // introduced to speed sync times.
@ -444,16 +454,16 @@ func (s *Ethereum) StopMining() {
func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
func (s *Ethereum) Miner() *miner.Miner { return s.miner } func (s *Ethereum) Miner() *miner.Miner { return s.miner }
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) Engine() consensus.Engine { return s.engine }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) IsListening() bool { return true } // Always listening
func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
func (s *Ethereum) NetVersion() uint64 { return s.networkID } func (s *Ethereum) NetVersion() uint64 { return s.networkID }
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerAPI { return s.externalSigner } func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerClient { return s.externalSigner }
// Protocols implements node.Service, returning all the currently configured // Protocols implements node.Service, returning all the currently configured
// network protocols to start. // network protocols to start.

View file

@ -183,7 +183,7 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string {
type PrivateAccountAPI struct { type PrivateAccountAPI struct {
nonceLock *AddrLocker nonceLock *AddrLocker
b Backend b Backend
extapi *ExternalSignerAPI // external signer, nil if not used extapi *ExternalSignerClient // external signer, nil if not used
} }
// NewPrivateAccountAPI create a new PrivateAccountAPI. // NewPrivateAccountAPI create a new PrivateAccountAPI.
@ -200,7 +200,7 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
func (s *PrivateAccountAPI) ListAccounts() []common.Address { func (s *PrivateAccountAPI) ListAccounts() []common.Address {
addresses := make([]common.Address, 0) // return [] instead of nil if empty addresses := make([]common.Address, 0) // return [] instead of nil if empty
if s.extapi != nil { if s.extapi != nil {
if accounts, err := s.extapi.listAccounts(); err == nil { if accounts, err := s.extapi.ListAccounts(); err == nil {
return accounts return accounts
} }
return addresses return addresses
@ -219,7 +219,7 @@ type rawWallet struct {
// NewAccount will create a new account and returns the address for the new account. // NewAccount will create a new account and returns the address for the new account.
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
return s.extapi.newAccount() return s.extapi.NewAccount()
} }
// fetchKeystore retrives the encrypted keystore from the account manager. // fetchKeystore retrives the encrypted keystore from the account manager.
@ -253,7 +253,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err return nil, err
} }
return s.extapi.signTransaction(ctx, args) return s.extapi.SignTransaction(ctx, args)
} }
// SendTransaction will create a transaction from the given arguments and // SendTransaction will create a transaction from the given arguments and
@ -806,7 +806,7 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa
type PublicTransactionPoolAPI struct { type PublicTransactionPoolAPI struct {
b Backend b Backend
nonceLock *AddrLocker nonceLock *AddrLocker
extapi *ExternalSignerAPI // external signer, nil if not used extapi *ExternalSignerClient // external signer, nil if not used
} }
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
@ -1057,7 +1057,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
signed, err := s.extapi.signTransaction(ctx, args) signed, err := s.extapi.SignTransaction(ctx, args)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -1114,7 +1114,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
err error err error
) )
tx, err = s.extapi.signTransaction(ctx, args) tx, err = s.extapi.SignTransaction(ctx, args)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1166,7 +1166,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
if gasLimit != nil && *gasLimit != 0 { if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit sendArgs.Gas = gasLimit
} }
signedTx, err = s.extapi.signTransaction(ctx, sendArgs) signedTx, err = s.extapi.SignTransaction(ctx, sendArgs)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -1300,24 +1300,24 @@ func (s *PublicNetAPI) Version() string {
return fmt.Sprintf("%d", s.networkVersion) return fmt.Sprintf("%d", s.networkVersion)
} }
// ExternalSignerAPI provides an API to interact with an external signer (clef) // ExternalSignerClient provides an API to interact with an external signer (clef)
// It proxies request to the external signer while forwarding relevant // It proxies request to the external signer while forwarding relevant
// request headers // request headers
type ExternalSignerAPI struct { type ExternalSignerClient struct {
client *rpc.Client client *rpc.Client
} }
func NewExternalSigner(endpoint string) (*ExternalSignerAPI, error) { func NewExternalSigner(endpoint string) (*ExternalSignerClient, error) {
client, err := rpc.DialHTTP(endpoint) client, err := rpc.DialHTTP(endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ExternalSignerAPI{ return &ExternalSignerClient{
client: client, client: client,
}, nil }, nil
} }
func (api *ExternalSignerAPI) signTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) { func (api *ExternalSignerClient) SignTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) {
if api == nil{ if api == nil{
return nil, errors.New("External API not initialized") return nil, errors.New("External API not initialized")
} }
@ -1327,7 +1327,7 @@ func (api *ExternalSignerAPI) signTransaction(ctx context.Context, args SendTxAr
} }
return res.Tx, nil return res.Tx, nil
} }
func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { func (api *ExternalSignerClient) ListAccounts() ([]common.Address, error) {
if api == nil{ if api == nil{
return []common.Address{}, errors.New("External API not initialized") return []common.Address{}, errors.New("External API not initialized")
} }
@ -1337,15 +1337,29 @@ func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) {
} }
return res, nil return res, nil
} }
func (api *ExternalSignerAPI) newAccount() (common.Address, error) { func (api *ExternalSignerClient) NewAccount() (common.Address, error) {
if api == nil{ if api == nil{
return common.Address{}, errors.New("External API not initialized") return common.Address{}, errors.New("External API not initialized")
} }
var res accounts.Account var res accounts.Account
if err := api.client.Call(&res, "account_new"); err != nil { if err := api.client.Call(&res, "account_new"); err != nil {
return res.Address, nil return common.Address{}, err
} }
return common.Address{}, nil return res.Address, nil
}
func (api *ExternalSignerClient) SignCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) {
if api == nil{
return nil, errors.New("External API not initialized")
}
var sig hexutil.Bytes
if err := api.client.Call(&sig, "account_signData", "application/clique", a, rlpBlock); err != nil {
return nil, err
}
if sig[64] != 27 && sig[64] != 28 {
return nil, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
}
sig[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
return sig, nil
} }

View file

@ -42,7 +42,7 @@ type Backend interface {
SuggestPrice(ctx context.Context) (*big.Int, error) SuggestPrice(ctx context.Context) (*big.Int, error)
ChainDb() ethdb.Database ChainDb() ethdb.Database
EventMux() *event.TypeMux EventMux() *event.TypeMux
ExternalSigner() *ExternalSignerAPI ExternalSigner() *ExternalSignerClient
// BlockChain API // BlockChain API
SetHead(number uint64) SetHead(number uint64)

View file

@ -183,7 +183,7 @@ func (b *LesApiBackend) EventMux() *event.TypeMux {
return b.eth.eventMux return b.eth.eventMux
} }
func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerAPI{ func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerClient {
return b.eth.externalSigner return b.eth.externalSigner
} }

View file

@ -68,7 +68,7 @@ type LightEthereum struct {
eventMux *event.TypeMux eventMux *event.TypeMux
engine consensus.Engine engine consensus.Engine
externalSigner *ethapi.ExternalSignerAPI externalSigner *ethapi.ExternalSignerClient
networkId uint64 networkId uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI

View file

@ -40,7 +40,7 @@ import (
type Node struct { type Node struct {
eventmux *event.TypeMux // Event multiplexer used between the services of a stack eventmux *event.TypeMux // Event multiplexer used between the services of a stack
config *Config config *Config
extapi *ethapi.ExternalSignerAPI// If non-empty, the ipc-path or http(s)-url to an external signer extapi *ethapi.ExternalSignerClient // If non-empty, the ipc-path or http(s)-url to an external signer
instanceDirLock flock.Releaser // prevents concurrent use of instance directory instanceDirLock flock.Releaser // prevents concurrent use of instance directory
@ -554,11 +554,11 @@ func (n *Node) ResolvePath(x string) string {
return n.config.ResolvePath(x) return n.config.ResolvePath(x)
} }
func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerAPI){ func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerClient){
n.extapi = api n.extapi = api
} }
func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerAPI{ func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerClient {
return n.extapi return n.extapi
} }
// apis returns the collection of RPC descriptors this node offers. // apis returns the collection of RPC descriptors this node offers.