rpc, whisper, xeth: fix RPC message retrieval data race

This commit is contained in:
Péter Szilágyi 2015-04-20 14:56:38 +03:00
parent 5aa523e32b
commit 7948cc0029
6 changed files with 119 additions and 13 deletions

@ -467,7 +467,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
return err return err
} }
*reply = api.xeth().Whisper().Messages(args.Id) *reply = api.xeth().Messages(args.Id)
// case "eth_register": // case "eth_register":
// // Placeholder for actual type // // Placeholder for actual type

@ -73,6 +73,7 @@ func (self *Envelope) Open(key *ecdsa.PrivateKey) (msg *Message, err error) {
message := &Message{ message := &Message{
Flags: data[0], Flags: data[0],
Sent: int64(self.Expiry - self.TTL), Sent: int64(self.Expiry - self.TTL),
Hash: self.Hash(),
} }
data = data[1:] data = data[1:]

36
whisper/envelope_test.go Normal file

@ -0,0 +1,36 @@
package whisper
import (
"bytes"
"testing"
)
func TestEnvelopeOpen(t *testing.T) {
payload := []byte("hello world")
message := NewMessage(payload)
envelope, err := message.Wrap(DefaultPoW, Options{})
if err != nil {
t.Fatalf("failed to wrap message: %v", err)
}
opened, err := envelope.Open(nil)
if err != nil {
t.Fatalf("failed to open envelope: %v.", err)
}
if opened.Flags != message.Flags {
t.Fatalf("flags mismatch: have %d, want %d", opened.Flags, message.Flags)
}
if bytes.Compare(opened.Signature, message.Signature) != 0 {
t.Fatalf("signature mismatch: have 0x%x, want 0x%x", opened.Signature, message.Signature)
}
if bytes.Compare(opened.Payload, message.Payload) != 0 {
t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, message.Payload)
}
if opened.Sent != message.Sent {
t.Fatalf("send time mismatch: have %d, want %d", opened.Sent, message.Sent)
}
if opened.Hash != envelope.Hash() {
t.Fatalf("message hash mismatch: have 0x%x, want 0x%x", opened.Hash, envelope.Hash())
}
}

@ -8,12 +8,13 @@ import (
"math/rand" "math/rand"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
) )
// Message represents an end-user data packet to trasmit through the Whisper // Message represents an end-user data packet to transmit through the Whisper
// protocol. These are wrapped into Envelopes that need not be understood by // protocol. These are wrapped into Envelopes that need not be understood by
// intermediate nodes, just forwarded. // intermediate nodes, just forwarded.
type Message struct { type Message struct {
@ -22,7 +23,8 @@ type Message struct {
Payload []byte Payload []byte
Sent int64 Sent int64
To *ecdsa.PublicKey To *ecdsa.PublicKey // Message recipient (identity used to decode the message)
Hash common.Hash // Message envelope hash to act as a unique id in de-duplication
} }
// Options specifies the exact way a message should be wrapped into an Envelope. // Options specifies the exact way a message should be wrapped into an Envelope.

@ -1,26 +1,84 @@
// Contains the external API side message filter for watching, pooling and polling // Contains the external API side message filter for watching, pooling and polling
// matched whisper messages. // matched whisper messages, also serializing data access to avoid duplications.
package xeth package xeth
import "time" import (
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
)
// whisperFilter is the message cache matching a specific filter, accumulating // whisperFilter is the message cache matching a specific filter, accumulating
// inbound messages until the are requested by the client. // inbound messages until the are requested by the client.
type whisperFilter struct { type whisperFilter struct {
id int // Filter identifier id int // Filter identifier for old message retrieval
ref *Whisper // Whisper reference for old message retrieval
cache []WhisperMessage // Cache of messages not yet polled cache []WhisperMessage // Cache of messages not yet polled
timeout time.Time // Time when the last message batch was queries skip map[common.Hash]struct{} // List of retrieved messages to avoid duplication
update time.Time // Time of the last message query
lock sync.RWMutex // Lock protecting the filter internals
}
// newWhisperFilter creates a new serialized, poll based whisper topic filter.
func newWhisperFilter(id int, ref *Whisper) *whisperFilter {
return &whisperFilter{
id: id,
ref: ref,
update: time.Now(),
skip: make(map[common.Hash]struct{}),
}
}
// messages retrieves all the cached messages from the entire pool matching the
// filter, resetting the filter's change buffer.
func (w *whisperFilter) messages() []WhisperMessage {
w.lock.Lock()
defer w.lock.Unlock()
w.cache = nil
w.update = time.Now()
w.skip = make(map[common.Hash]struct{})
messages := w.ref.Messages(w.id)
for _, message := range messages {
w.skip[message.ref.Hash] = struct{}{}
}
return messages
} }
// insert injects a new batch of messages into the filter cache. // insert injects a new batch of messages into the filter cache.
func (w *whisperFilter) insert(msgs ...WhisperMessage) { func (w *whisperFilter) insert(messages ...WhisperMessage) {
w.cache = append(w.cache, msgs...) w.lock.Lock()
defer w.lock.Unlock()
for _, message := range messages {
if _, ok := w.skip[message.ref.Hash]; !ok {
w.cache = append(w.cache, messages...)
}
}
} }
// retrieve fetches all the cached messages from the filter. // retrieve fetches all the cached messages from the filter.
func (w *whisperFilter) retrieve() (messages []WhisperMessage) { func (w *whisperFilter) retrieve() (messages []WhisperMessage) {
w.lock.Lock()
defer w.lock.Unlock()
messages, w.cache = w.cache, nil messages, w.cache = w.cache, nil
w.timeout = time.Now() w.update = time.Now()
return return
} }
// activity returns the last time instance when client requests were executed on
// the filter.
func (w *whisperFilter) activity() time.Time {
w.lock.RLock()
defer w.lock.RUnlock()
return w.update
}

@ -97,7 +97,7 @@ done:
} }
for id, filter := range self.messages { for id, filter := range self.messages {
if time.Since(filter.timeout) > filterTickerTime { if time.Since(filter.activity()) > filterTickerTime {
self.Whisper().Unwatch(id) self.Whisper().Unwatch(id)
delete(self.messages, id) delete(self.messages, id)
} }
@ -461,7 +461,7 @@ func (p *XEth) NewWhisperFilter(to, from string, topics []string) int {
p.messages[id].insert(msg) p.messages[id].insert(msg)
} }
id = p.Whisper().Watch(to, from, topics, callback) id = p.Whisper().Watch(to, from, topics, callback)
p.messages[id] = &whisperFilter{timeout: time.Now()} p.messages[id] = newWhisperFilter(id, p.Whisper())
return id return id
} }
@ -481,7 +481,16 @@ func (self *XEth) MessagesChanged(id int) []WhisperMessage {
if self.messages[id] != nil { if self.messages[id] != nil {
return self.messages[id].retrieve() return self.messages[id].retrieve()
} }
return nil
}
func (self *XEth) Messages(id int) []WhisperMessage {
self.messagesMut.Lock()
defer self.messagesMut.Unlock()
if self.messages[id] != nil {
return self.messages[id].messages()
}
return nil return nil
} }