From 9e3f27fc186572e0ff7f2847413d0cc11f3d47b5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 25 Sep 2018 11:07:18 +0200 Subject: [PATCH] clique, signer: implement clique signing for clef --- consensus/clique/clique.go | 8 +++--- eth/api_backend.go | 2 +- eth/backend.go | 42 ++++++++++++++++++++------------ internal/ethapi/api.go | 50 ++++++++++++++++++++++++-------------- internal/ethapi/backend.go | 2 +- les/api_backend.go | 2 +- les/backend.go | 2 +- node/node.go | 6 ++--- 8 files changed, 69 insertions(+), 45 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index eae09f91df..af6494c90e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -39,7 +39,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - lru "github.com/hashicorp/golang-lru" + "github.com/hashicorp/golang-lru" ) const ( @@ -136,9 +136,9 @@ var ( 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. -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 // 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)) } // 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 { return err } diff --git a/eth/api_backend.go b/eth/api_backend.go index 6609d6badf..b9dd993dc8 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -219,7 +219,7 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } -func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerAPI { +func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerClient { return b.eth.externalSigner } diff --git a/eth/backend.go b/eth/backend.go index f53c0c8c0d..9a15d1f557 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,6 +20,7 @@ package eth import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts" "math/big" "runtime" "sync" @@ -76,7 +77,7 @@ type Ethereum struct { eventMux *event.TypeMux engine consensus.Engine - externalSigner *ethapi.ExternalSignerAPI + externalSigner *ethapi.ExternalSignerClient bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests 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 // 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 if config.SyncMode == downloader.LightSync { 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) return fmt.Errorf("etherbase missing: %v", err) } - if _, ok := s.engine.(*clique.Clique); ok { - //TODO @holiman - log.Error("Etherbase account unavailable locally", "err", err) - return fmt.Errorf("signer not configured: %v", err) + if clique, ok := s.engine.(*clique.Clique); ok { + if s.ExternalSigner() != nil { + signerFn := func(a accounts.Account, header *types.Header) ([]byte, error) { + 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 // 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) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Engine() consensus.Engine { return s.engine } -func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } -func (s *Ethereum) NetVersion() uint64 { return s.networkID } -func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } -func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerAPI { return s.externalSigner } +func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Engine() consensus.Engine { return s.engine } +func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } +func (s *Ethereum) NetVersion() uint64 { return s.networkID } +func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerClient { return s.externalSigner } // Protocols implements node.Service, returning all the currently configured // network protocols to start. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4242a8b106..cccfbe8c7e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -183,7 +183,7 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string { type PrivateAccountAPI struct { nonceLock *AddrLocker b Backend - extapi *ExternalSignerAPI // external signer, nil if not used + extapi *ExternalSignerClient // external signer, nil if not used } // NewPrivateAccountAPI create a new PrivateAccountAPI. @@ -200,7 +200,7 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI { func (s *PrivateAccountAPI) ListAccounts() []common.Address { addresses := make([]common.Address, 0) // return [] instead of nil if empty if s.extapi != nil { - if accounts, err := s.extapi.listAccounts(); err == nil { + if accounts, err := s.extapi.ListAccounts(); err == nil { return accounts } return addresses @@ -219,7 +219,7 @@ type rawWallet struct { // NewAccount will create a new account and returns the address for the new account. 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. @@ -253,7 +253,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs if err := args.setDefaults(ctx, s.b); err != nil { 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 @@ -806,7 +806,7 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa type PublicTransactionPoolAPI struct { b Backend 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. @@ -1057,7 +1057,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen if err := args.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } - signed, err := s.extapi.signTransaction(ctx, args) + signed, err := s.extapi.SignTransaction(ctx, args) if err != nil { return common.Hash{}, err } @@ -1114,7 +1114,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen err error ) - tx, err = s.extapi.signTransaction(ctx, args) + tx, err = s.extapi.SignTransaction(ctx, args) if err != nil { return nil, err @@ -1166,7 +1166,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err = s.extapi.signTransaction(ctx, sendArgs) + signedTx, err = s.extapi.SignTransaction(ctx, sendArgs) if err != nil { return common.Hash{}, err } @@ -1300,24 +1300,24 @@ func (s *PublicNetAPI) Version() string { 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 // request headers -type ExternalSignerAPI struct { +type ExternalSignerClient struct { client *rpc.Client } -func NewExternalSigner(endpoint string) (*ExternalSignerAPI, error) { +func NewExternalSigner(endpoint string) (*ExternalSignerClient, error) { client, err := rpc.DialHTTP(endpoint) if err != nil { return nil, err } - return &ExternalSignerAPI{ + return &ExternalSignerClient{ client: client, }, 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{ 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 } -func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { +func (api *ExternalSignerClient) ListAccounts() ([]common.Address, error) { if api == nil{ return []common.Address{}, errors.New("External API not initialized") } @@ -1337,15 +1337,29 @@ func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { } return res, nil } -func (api *ExternalSignerAPI) newAccount() (common.Address, error) { +func (api *ExternalSignerClient) NewAccount() (common.Address, error) { if api == nil{ return common.Address{}, errors.New("External API not initialized") } var res accounts.Account 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 } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 2e785b853c..8312a3a25f 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -42,7 +42,7 @@ type Backend interface { SuggestPrice(ctx context.Context) (*big.Int, error) ChainDb() ethdb.Database EventMux() *event.TypeMux - ExternalSigner() *ExternalSignerAPI + ExternalSigner() *ExternalSignerClient // BlockChain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 2e1dfc5342..e65c15008b 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -183,7 +183,7 @@ func (b *LesApiBackend) EventMux() *event.TypeMux { return b.eth.eventMux } -func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerAPI{ +func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerClient { return b.eth.externalSigner } diff --git a/les/backend.go b/les/backend.go index 5ebcf977da..41f489db8c 100644 --- a/les/backend.go +++ b/les/backend.go @@ -68,7 +68,7 @@ type LightEthereum struct { eventMux *event.TypeMux engine consensus.Engine - externalSigner *ethapi.ExternalSignerAPI + externalSigner *ethapi.ExternalSignerClient networkId uint64 netRPCService *ethapi.PublicNetAPI diff --git a/node/node.go b/node/node.go index 60923b2c07..4bde7fbb9e 100644 --- a/node/node.go +++ b/node/node.go @@ -40,7 +40,7 @@ import ( type Node struct { eventmux *event.TypeMux // Event multiplexer used between the services of a stack 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 @@ -554,11 +554,11 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } -func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerAPI){ +func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerClient){ n.extapi = api } -func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerAPI{ +func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerClient { return n.extapi } // apis returns the collection of RPC descriptors this node offers.