node: add GuardianSetState and verify heartbeat signature
Bug: certusone/wormhole#267 Change-Id: Ica8015fbbd52506d800670d933051f410bb1caa7
This commit is contained in:
parent
08a8f390ca
commit
3af233e3eb
|
@ -368,6 +368,9 @@ func runBridge(cmd *cobra.Command, args []string) {
|
||||||
// Injected VAAs (manually generated rather than created via observation)
|
// Injected VAAs (manually generated rather than created via observation)
|
||||||
injectC := make(chan *vaa.VAA)
|
injectC := make(chan *vaa.VAA)
|
||||||
|
|
||||||
|
// Guardian set state managed by processor
|
||||||
|
gst := &common.GuardianSetState{}
|
||||||
|
|
||||||
// Load p2p private key
|
// Load p2p private key
|
||||||
var priv crypto.PrivKey
|
var priv crypto.PrivKey
|
||||||
if *unsafeDevMode {
|
if *unsafeDevMode {
|
||||||
|
@ -404,7 +407,7 @@ func runBridge(cmd *cobra.Command, args []string) {
|
||||||
// Run supervisor.
|
// Run supervisor.
|
||||||
supervisor.New(rootCtx, logger, func(ctx context.Context) error {
|
supervisor.New(rootCtx, logger, func(ctx context.Context) error {
|
||||||
if err := supervisor.Run(ctx, "p2p", p2p.Run(
|
if err := supervisor.Run(ctx, "p2p", p2p.Run(
|
||||||
obsvC, sendC, rawHeartbeatListeners, priv, gk, *p2pPort, *p2pNetworkID, *p2pBootstrap, *nodeName, rootCtxCancel)); err != nil {
|
obsvC, sendC, rawHeartbeatListeners, priv, gk, gst, *p2pPort, *p2pNetworkID, *p2pBootstrap, *nodeName, rootCtxCancel)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,6 +446,7 @@ func runBridge(cmd *cobra.Command, args []string) {
|
||||||
obsvC,
|
obsvC,
|
||||||
injectC,
|
injectC,
|
||||||
gk,
|
gk,
|
||||||
|
gst,
|
||||||
*unsafeDevMode,
|
*unsafeDevMode,
|
||||||
*devNumGuardians,
|
*devNumGuardians,
|
||||||
*ethRPC,
|
*ethRPC,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Matching constants:
|
// Matching constants:
|
||||||
|
@ -39,3 +40,22 @@ func (g *GuardianSet) KeyIndex(addr common.Address) (int, bool) {
|
||||||
|
|
||||||
return -1, false
|
return -1, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GuardianSetState struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
current *GuardianSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *GuardianSetState) Set(set *GuardianSet) {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
|
|
||||||
|
st.current = set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *GuardianSetState) Get() *GuardianSet {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
|
|
||||||
|
return st.current
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package p2p
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
bridge_common "github.com/certusone/wormhole/bridge/pkg/common"
|
||||||
"github.com/certusone/wormhole/bridge/pkg/version"
|
"github.com/certusone/wormhole/bridge/pkg/version"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
@ -57,17 +59,7 @@ func heartbeatDigest(b []byte) common.Hash {
|
||||||
return ethcrypto.Keccak256Hash(append(heartbeatMessagePrefix, b...))
|
return ethcrypto.Keccak256Hash(append(heartbeatMessagePrefix, b...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(
|
func Run(obsvC chan *gossipv1.SignedObservation, sendC chan []byte, rawHeartbeatListeners *publicrpc.RawHeartbeatConns, priv crypto.PrivKey, gk *ecdsa.PrivateKey, gst *bridge_common.GuardianSetState, port uint, networkID string, bootstrapPeers string, nodeName string, rootCtxCancel context.CancelFunc) func(ctx context.Context) error {
|
||||||
obsvC chan *gossipv1.SignedObservation,
|
|
||||||
sendC chan []byte, rawHeartbeatListeners *publicrpc.RawHeartbeatConns,
|
|
||||||
priv crypto.PrivKey,
|
|
||||||
gk *ecdsa.PrivateKey,
|
|
||||||
port uint,
|
|
||||||
networkID string,
|
|
||||||
bootstrapPeers string,
|
|
||||||
nodeName string,
|
|
||||||
rootCtxCancel context.CancelFunc) func(ctx context.Context) error {
|
|
||||||
|
|
||||||
return func(ctx context.Context) (re error) {
|
return func(ctx context.Context) (re error) {
|
||||||
logger := supervisor.Logger(ctx)
|
logger := supervisor.Logger(ctx)
|
||||||
|
|
||||||
|
@ -301,9 +293,16 @@ func Run(
|
||||||
p2pMessagesReceived.WithLabelValues("heartbeat").Inc()
|
p2pMessagesReceived.WithLabelValues("heartbeat").Inc()
|
||||||
case *gossipv1.GossipMessage_SignedHeartbeat:
|
case *gossipv1.GossipMessage_SignedHeartbeat:
|
||||||
s := m.SignedHeartbeat
|
s := m.SignedHeartbeat
|
||||||
if heartbeat, err := processSignedHeartbeat(s); err != nil {
|
gs := gst.Get()
|
||||||
|
if gs == nil {
|
||||||
|
// No valid guardian set yet - dropping heartbeat
|
||||||
|
logger.Debug("skipping heartbeat - no guardian set",
|
||||||
|
zap.Any("value", s),
|
||||||
|
zap.String("from", envelope.GetFrom().String()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if heartbeat, err := processSignedHeartbeat(s, gs); err != nil {
|
||||||
p2pMessagesReceived.WithLabelValues("invalid_heartbeat").Inc()
|
p2pMessagesReceived.WithLabelValues("invalid_heartbeat").Inc()
|
||||||
|
|
||||||
logger.Warn("invalid signed heartbeat received",
|
logger.Warn("invalid signed heartbeat received",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Any("payload", msg.Message),
|
zap.Any("payload", msg.Message),
|
||||||
|
@ -331,11 +330,29 @@ func Run(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processSignedHeartbeat(s *gossipv1.SignedHeartbeat) (*gossipv1.Heartbeat, error) {
|
func processSignedHeartbeat(s *gossipv1.SignedHeartbeat, gs *bridge_common.GuardianSet) (*gossipv1.Heartbeat, error) {
|
||||||
// TODO: verify signature here
|
envelopeAddr := common.BytesToAddress(s.GuardianAddr)
|
||||||
|
idx, ok := gs.KeyIndex(envelopeAddr)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid message: %s not in guardian set", envelopeAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := gs.Keys[idx]
|
||||||
|
|
||||||
|
digest := heartbeatDigest(s.Heartbeat)
|
||||||
|
|
||||||
|
pubKey, err := ethcrypto.Ecrecover(digest.Bytes(), s.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to recover public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
signerAddr := common.BytesToAddress(ethcrypto.Keccak256(pubKey[1:])[12:])
|
||||||
|
if pk != signerAddr {
|
||||||
|
return nil, fmt.Errorf("invalid signer: %v", signerAddr)
|
||||||
|
}
|
||||||
|
|
||||||
var h gossipv1.Heartbeat
|
var h gossipv1.Heartbeat
|
||||||
err := proto.Unmarshal(s.Heartbeat, &h)
|
err = proto.Unmarshal(s.Heartbeat, &h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal heartbeat: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal heartbeat: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,10 @@ type Processor struct {
|
||||||
|
|
||||||
// gs is the currently valid guardian set
|
// gs is the currently valid guardian set
|
||||||
gs *common.GuardianSet
|
gs *common.GuardianSet
|
||||||
|
// gst is managed by the processor and allows concurrent access to the
|
||||||
|
// guardian set by other components.
|
||||||
|
gst *common.GuardianSetState
|
||||||
|
|
||||||
// state is the current runtime VAA view
|
// state is the current runtime VAA view
|
||||||
state *aggregationState
|
state *aggregationState
|
||||||
// gk pk as eth address
|
// gk pk as eth address
|
||||||
|
@ -100,6 +104,7 @@ func NewProcessor(
|
||||||
obsvC chan *gossipv1.SignedObservation,
|
obsvC chan *gossipv1.SignedObservation,
|
||||||
injectC chan *vaa.VAA,
|
injectC chan *vaa.VAA,
|
||||||
gk *ecdsa.PrivateKey,
|
gk *ecdsa.PrivateKey,
|
||||||
|
gst *common.GuardianSetState,
|
||||||
devnetMode bool,
|
devnetMode bool,
|
||||||
devnetNumGuardians uint,
|
devnetNumGuardians uint,
|
||||||
devnetEthRPC string,
|
devnetEthRPC string,
|
||||||
|
@ -114,6 +119,7 @@ func NewProcessor(
|
||||||
obsvC: obsvC,
|
obsvC: obsvC,
|
||||||
injectC: injectC,
|
injectC: injectC,
|
||||||
gk: gk,
|
gk: gk,
|
||||||
|
gst: gst,
|
||||||
devnetMode: devnetMode,
|
devnetMode: devnetMode,
|
||||||
devnetNumGuardians: devnetNumGuardians,
|
devnetNumGuardians: devnetNumGuardians,
|
||||||
devnetEthRPC: devnetEthRPC,
|
devnetEthRPC: devnetEthRPC,
|
||||||
|
@ -140,6 +146,7 @@ func (p *Processor) Run(ctx context.Context) error {
|
||||||
p.logger.Info("guardian set updated",
|
p.logger.Info("guardian set updated",
|
||||||
zap.Strings("set", p.gs.KeysAsHexStrings()),
|
zap.Strings("set", p.gs.KeysAsHexStrings()),
|
||||||
zap.Uint32("index", p.gs.Index))
|
zap.Uint32("index", p.gs.Index))
|
||||||
|
p.gst.Set(p.gs)
|
||||||
case k := <-p.lockC:
|
case k := <-p.lockC:
|
||||||
p.handleMessage(ctx, k)
|
p.handleMessage(ctx, k)
|
||||||
case v := <-p.injectC:
|
case v := <-p.injectC:
|
||||||
|
|
Loading…
Reference in New Issue