ethereum Whisper6 源码解读

首先看看一个Whisper6的初始化过程

// New creates a Whisper client ready to communicate through the Ethereum P2P network.
func New(cfg *Config) *Whisper {
    if cfg == nil {
        cfg = &DefaultConfig
    }

    whisper := &Whisper{
        privateKeys:   make(map[string]*ecdsa.PrivateKey),
        symKeys:       make(map[string][]byte),
        envelopes:     make(map[common.Hash]*Envelope),
        expirations:   make(map[uint32]*set.SetNonTS),
        peers:         make(map[*Peer]struct{}),
        messageQueue:  make(chan *Envelope, messageQueueLimit),
        p2pMsgQueue:   make(chan *Envelope, messageQueueLimit),
        quit:          make(chan struct{}),
        syncAllowance: DefaultSyncAllowance,
    }

    whisper.filters = NewFilters(whisper)

    whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW)
    whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize)
    whisper.settings.Store(overflowIdx, false)

    // p2p whisper sub protocol handler
    whisper.protocol = p2p.Protocol{
        Name:    ProtocolName,
        Version: uint(ProtocolVersion),
        Length:  NumberOfMessageCodes,
        Run:     whisper.HandlePeer,
        NodeInfo: func() interface{} {
            return map[string]interface{}{
                "version":        ProtocolVersionStr,
                "maxMessageSize": whisper.MaxMessageSize(),
                "minimumPoW":     whisper.MinPow(),
            }
        },
    }

    return whisper
}

该方法初始化了一个whisper客户端,指定了处理whisper消息的队列大小,该客户端接收到消息的最小pow值限制,处理一条whisper消息最大允许时间,一条whisper消息的最大长度限制,并且初始化了whisper客户端中的p2p协议,其中 whisper.HandlePeerethereum/eth/handle.go中的handleMsg方法逻辑类似,也就是根据不同的message code处理接受到的不同whisper消息。

到此,一个whisper客户端就已经初始化完成了,在发送whisper消息之前,我们还需要一个whisper邮件处理服务的初始化

func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) error {
    var err error
    if len(path) == 0 {
        return fmt.Errorf("DB file is not specified")
    }

    if len(password) == 0 {
        return fmt.Errorf("password is not specified")
    }

    s.db, err = leveldb.OpenFile(path, nil)
    if err != nil {
        return fmt.Errorf("open DB file: %s", err)
    }

    s.w = shh
    s.pow = pow

    MailServerKeyID, err := s.w.AddSymKeyFromPassword(password)
    if err != nil {
        return fmt.Errorf("create symmetric key: %s", err)
    }
    s.key, err = s.w.GetSymKey(MailServerKeyID)
    if err != nil {
        return fmt.Errorf("save symmetric key: %s", err)
    }
    return nil
}

该方法指定该邮件服务处理的pow值,将whisper客户端注册上去,同时随机生成一个id作为该mailServer的标识,并以指定的密码生成一个对称Symmetric Key。该mailServer会将收到每一个whisper消息存储在leveldb中。

func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) []*whisper.Envelope {}

这个方法的处理逻辑是从给定的边界值[low,upper]中,从mailServer邮件服务的db中,根据布隆值是否相等寻找对应的信封(envelope),如果找到,并且指定了peer节点,则将找到的这个信封通过ethereum的p2p网络发送给这个peer,如果找到但是没有指定peer,则作为返回值返回,需要注意的是bloom值是根据信封的topic按一定规则计算而得的,所以通过指定的布隆值去寻找,在mailServer中会寻找到不止一个信封。

到此whisper客户端以及处理whisper消息的mail server都初始化完成,那么我们看看如果发送一个whisper消息(后面都用envelope信封替代)

// Post a message on the Whisper network.
func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) {
    ...
}

该方法接受从控制台发送到whisper客户端的消息,对称加密或非对称加密消息只能指定一种,否则无效,让我们看构造的消息体参数

params := &MessageParams{
        TTL:      req.TTL,
        Payload:  req.Payload,
        Padding:  req.Padding,
        WorkTime: req.PowTime,
        PoW:      req.PowTarget,
        Topic:    req.Topic,
    }

消息参数中指定了消息的主题,过期时间(ttl),消息内容(payload),该消息的pow值,计算pow值限定的时间(workTime)。

// Wrap bundles the message into an Envelope to transmit over the network.
func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) {
    if options.TTL == 0 {
        options.TTL = DefaultTTL
    }
    if options.Src != nil {
        if err = msg.sign(options.Src); err != nil {
            return nil, err
        }
    }
    var nonce []byte
    if options.Dst != nil {
        err = msg.encryptAsymmetric(options.Dst)
    } else if options.KeySym != nil {
        nonce, err = msg.encryptSymmetric(options.KeySym)
    } else {
        err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
    }
    if err != nil {
        return nil, err
    }

    envelope = NewEnvelope(options.TTL, options.Topic, nonce, msg)
    if err = envelope.Seal(options); err != nil {
        return nil, err
    }
    return envelope, nil
}

该方法中根据params指定的加密形式进行消息的加密动作(对称:KeySym,非对称:Src,Dst),并且根据加密完的msg等参数构造了一个信封,Seal根据给定的pow值在限制的workTime进行校验,如果在规定时间内计算出的最大pow值小于给定的pow值,则说明msg指定的难度值过大,抛出异常。

如果wrap方法正常,则进行pow难度值校验,如果msg给定的pow值小于whisper客户端的最小pow值,则拒绝消息处理。如果校验正常,并且msg没有指定接受者peer,则将该信封投递到whisper客户端注册的邮件服务中。如果指定了peer,则通过p2p网络,将该信封投递出去。注意这种发送走的是peer-to-peer直连,不进行转发(w.SendP2PDirect(p, envelope)),到此整个发送逻辑已经结束。

接收节点收到消息后进行解码消息动作,whisper中支持ecdsa私钥加密或者AES-GCM-256加密(即对称加密和非对称加密),但是两种不能同时存在,而且必须存在一种。最后我们看看接收端处理逻辑

// runMessageLoop reads and processes inbound messages directly to merge into client-global state.
func (w *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
    for {
        // fetch the next packet
        packet, err := rw.ReadMsg()
        if err != nil {
            log.Info("message loop", "peer", p.peer.ID(), "err", err)
            return err
        }
        if packet.Size > w.MaxMessageSize() {
            log.Warn("oversized message received", "peer", p.peer.ID())
            return errors.New("oversized message received")
        }

        switch packet.Code {
        case statusCode:
            // this should not happen, but no need to panic; just ignore this message.
            log.Warn("unxepected status message received", "peer", p.peer.ID())
        case messagesCode:
            // decode the contained envelopes
            var envelope Envelope
            if err := packet.Decode(&envelope); err != nil {
                log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err)
                return errors.New("invalid envelope")
            }
            cached, err := w.add(&envelope)
            if err != nil {
                log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err)
                return errors.New("invalid envelope")
            }
            if cached {
                p.mark(&envelope)
            }
       default:
            // New message types might be implemented in the future versions of Whisper.
            // For forward compatibility, just ignore.
        }

        packet.Discard()
    }
}

whisper消息根据对应的message code进行到第二个case中,进行rlp解码,如果解码无误,则将信封消息加入到消息处理内存map中。如果成功添加,则标记该信封为已处理(防重复处理),如果接收者的whisper客户端注册了mailServer,则将该消息存储到邮件服务的leveldb中。

whisper.add方法中通过管道写到了消息处理队列中(postEvent --> messageQueue

func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
    var msg *ReceivedMessage

    fs.mutex.RLock()
    defer fs.mutex.RUnlock()

    i := -1 // only used for logging info
    for _, watcher := range fs.watchers {
        i++
        if p2pMessage && !watcher.AllowP2P {
            log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i))
            continue
        }

        var match bool
        if msg != nil {
            match = watcher.MatchMessage(msg)
        } else {
            match = watcher.MatchEnvelope(env)
            if match {
                msg = env.Open(watcher)
                if msg == nil {
                    log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", i)
                }
            } else {
                log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i)
            }
        }

        if match && msg != nil {
            log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
            if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
                watcher.Trigger(msg)
            }
        }
    }
}

该方法为管道接收端消息的处理逻辑,首先获取到所有对该消息主题感兴趣以及不管啥主题都接收的观察者fiilters,然后遍历所有观察者,对该信封消息进行处理, MatchEnvelope方法进行信封的pow值校验,如果小于观察者的最小pow值则不处理,如果信封所使用的加密算法与观察者期待的不同,也不进行处理,如果观察者希望接收的topic中不包含信封上指定的topic也不进行处理。

// Open tries to decrypt an envelope, and populates the message fields in case of success.
func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
    if watcher == nil {
        return nil
    }

    // The API interface forbids filters doing both symmetric and asymmetric encryption.
    if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() {
        return nil
    }

    if watcher.expectsAsymmetricEncryption() {
        msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
        if msg != nil {
            msg.Dst = &watcher.KeyAsym.PublicKey
        }
    } else if watcher.expectsSymmetricEncryption() {
        msg, _ = e.OpenSymmetric(watcher.KeySym)
        if msg != nil {
            msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
        }
    }

    if msg != nil {
        ok := msg.ValidateAndParse()
        if !ok {
            return nil
        }
        msg.Topic = e.Topic
        msg.PoW = e.PoW()
        msg.TTL = e.TTL
        msg.Sent = e.Expiry - e.TTL
        msg.EnvelopeHash = e.Hash()
    }
    return msg
}

上述逻辑判断如果都正常,则根据Open方法解密信封上的消息,如果一切正常则将解码的消息添加到观察者的消息存储的map中,等待处理(key为信封的hash值)。

id, err := api.w.Subscribe(&filter)
    if err != nil {
        return nil, err
    }
// create subscription and start waiting for message events
    rpcSub := notifier.CreateSubscription()
    go func() {
        // for now poll internally, refactor whisper internal for channel support
        ticker := time.NewTicker(250 * time.Millisecond)
        defer ticker.Stop()

        for {
            select {
            case <-ticker.C:
                if filter := api.w.GetFilter(id); filter != nil {
                    for _, rpcMessage := range toMessage(filter.Retrieve()) {
                        if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil {
                            log.Error("Failed to send notification", "err", err)
                        }
                    }
                }
            case <-rpcSub.Err():
                api.w.Unsubscribe(id)
                return
            case <-notifier.Closed():
                api.w.Unsubscribe(id)
                return
            }
        }
    }()

    return rpcSub, nil

该方法定时从whisper客户端中获取指定的观察者,如果还未取消订阅,则读取该观察者存储的所有信封消息,然后通过rpc的形式通知给订阅的客户端

到此,讲述了whisper6从发送消息,到p2p传输,以及接受者对消息的处理的整个流程,至于whisper5和whisper6的区别,从数据结构上来看,whisper6在此基础上增加了轻客户端的支持以及对一条whisper消息处理的最长时间限制(whisper5源码未细看)。

你可能感兴趣的:(ethereum Whisper6 源码解读)