mirror of https://github.com/ethereum/go-ethereum
Conflicts: rpc/api.gopull/821/head
commit
99027c79fe
@ -0,0 +1,142 @@ |
|||||||
|
package whisper |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/crypto/ecies" |
||||||
|
) |
||||||
|
|
||||||
|
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.Unix() != message.Sent.Unix() { |
||||||
|
t.Fatalf("send time mismatch: have %d, want %d", opened.Sent, message.Sent) |
||||||
|
} |
||||||
|
if opened.TTL/time.Second != DefaultTTL/time.Second { |
||||||
|
t.Fatalf("message TTL mismatch: have %v, want %v", opened.TTL, DefaultTTL) |
||||||
|
} |
||||||
|
|
||||||
|
if opened.Hash != envelope.Hash() { |
||||||
|
t.Fatalf("message hash mismatch: have 0x%x, want 0x%x", opened.Hash, envelope.Hash()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestEnvelopeAnonymousOpenUntargeted(t *testing.T) { |
||||||
|
payload := []byte("hello envelope") |
||||||
|
envelope, err := NewMessage(payload).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.To != nil { |
||||||
|
t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To) |
||||||
|
} |
||||||
|
if bytes.Compare(opened.Payload, payload) != 0 { |
||||||
|
t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestEnvelopeAnonymousOpenTargeted(t *testing.T) { |
||||||
|
key, err := crypto.GenerateKey() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to generate test identity: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
payload := []byte("hello envelope") |
||||||
|
envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{ |
||||||
|
To: &key.PublicKey, |
||||||
|
}) |
||||||
|
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.To != nil { |
||||||
|
t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To) |
||||||
|
} |
||||||
|
if bytes.Compare(opened.Payload, payload) == 0 { |
||||||
|
t.Fatalf("payload match, should have been encrypted: 0x%x", opened.Payload) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestEnvelopeIdentifiedOpenUntargeted(t *testing.T) { |
||||||
|
key, err := crypto.GenerateKey() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to generate test identity: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
payload := []byte("hello envelope") |
||||||
|
envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{}) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to wrap message: %v", err) |
||||||
|
} |
||||||
|
opened, err := envelope.Open(key) |
||||||
|
switch err { |
||||||
|
case nil: |
||||||
|
t.Fatalf("envelope opened with bad key: %v", opened) |
||||||
|
|
||||||
|
case ecies.ErrInvalidPublicKey: |
||||||
|
// Ok, key mismatch but opened
|
||||||
|
|
||||||
|
default: |
||||||
|
t.Fatalf("failed to open envelope: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
if opened.To != nil { |
||||||
|
t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To) |
||||||
|
} |
||||||
|
if bytes.Compare(opened.Payload, payload) != 0 { |
||||||
|
t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestEnvelopeIdentifiedOpenTargeted(t *testing.T) { |
||||||
|
key, err := crypto.GenerateKey() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to generate test identity: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
payload := []byte("hello envelope") |
||||||
|
envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{ |
||||||
|
To: &key.PublicKey, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to wrap message: %v", err) |
||||||
|
} |
||||||
|
opened, err := envelope.Open(key) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to open envelope: %v", err) |
||||||
|
} |
||||||
|
if opened.To != nil { |
||||||
|
t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To) |
||||||
|
} |
||||||
|
if bytes.Compare(opened.Payload, payload) != 0 { |
||||||
|
t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,199 @@ |
|||||||
|
package whisper |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
|
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
var filterTopicsCreationTests = []struct { |
||||||
|
topics [][]string |
||||||
|
filter [][][4]byte |
||||||
|
}{ |
||||||
|
{ // Simple topic filter
|
||||||
|
topics: [][]string{ |
||||||
|
{"abc", "def", "ghi"}, |
||||||
|
{"def"}, |
||||||
|
{"ghi", "abc"}, |
||||||
|
}, |
||||||
|
filter: [][][4]byte{ |
||||||
|
{{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9}}, |
||||||
|
{{0x34, 0x60, 0x7c, 0x9b}}, |
||||||
|
{{0x21, 0x41, 0x7d, 0xf9}, {0x4e, 0x03, 0x65, 0x7a}}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ // Wild-carded topic filter
|
||||||
|
topics: [][]string{ |
||||||
|
{"abc", "def", "ghi"}, |
||||||
|
{}, |
||||||
|
{""}, |
||||||
|
{"def"}, |
||||||
|
}, |
||||||
|
filter: [][][4]byte{ |
||||||
|
{{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9}}, |
||||||
|
{}, |
||||||
|
{}, |
||||||
|
{{0x34, 0x60, 0x7c, 0x9b}}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var filterTopicsCreationFlatTests = []struct { |
||||||
|
topics []string |
||||||
|
filter [][][4]byte |
||||||
|
}{ |
||||||
|
{ // Simple topic list
|
||||||
|
topics: []string{"abc", "def", "ghi"}, |
||||||
|
filter: [][][4]byte{ |
||||||
|
{{0x4e, 0x03, 0x65, 0x7a}}, |
||||||
|
{{0x34, 0x60, 0x7c, 0x9b}}, |
||||||
|
{{0x21, 0x41, 0x7d, 0xf9}}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ // Wild-carded topic list
|
||||||
|
topics: []string{"abc", "", "ghi"}, |
||||||
|
filter: [][][4]byte{ |
||||||
|
{{0x4e, 0x03, 0x65, 0x7a}}, |
||||||
|
{}, |
||||||
|
{{0x21, 0x41, 0x7d, 0xf9}}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestFilterTopicsCreation(t *testing.T) { |
||||||
|
// Check full filter creation
|
||||||
|
for i, tt := range filterTopicsCreationTests { |
||||||
|
// Check the textual creation
|
||||||
|
filter := NewFilterTopicsFromStrings(tt.topics...) |
||||||
|
if len(filter) != len(tt.topics) { |
||||||
|
t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics)) |
||||||
|
continue |
||||||
|
} |
||||||
|
for j, condition := range filter { |
||||||
|
if len(condition) != len(tt.filter[j]) { |
||||||
|
t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j])) |
||||||
|
continue |
||||||
|
} |
||||||
|
for k := 0; k < len(condition); k++ { |
||||||
|
if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 { |
||||||
|
t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k]) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Check the binary creation
|
||||||
|
binary := make([][][]byte, len(tt.topics)) |
||||||
|
for j, condition := range tt.topics { |
||||||
|
binary[j] = make([][]byte, len(condition)) |
||||||
|
for k, segment := range condition { |
||||||
|
binary[j][k] = []byte(segment) |
||||||
|
} |
||||||
|
} |
||||||
|
filter = NewFilterTopics(binary...) |
||||||
|
if len(filter) != len(tt.topics) { |
||||||
|
t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics)) |
||||||
|
continue |
||||||
|
} |
||||||
|
for j, condition := range filter { |
||||||
|
if len(condition) != len(tt.filter[j]) { |
||||||
|
t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j])) |
||||||
|
continue |
||||||
|
} |
||||||
|
for k := 0; k < len(condition); k++ { |
||||||
|
if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 { |
||||||
|
t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k]) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Check flat filter creation
|
||||||
|
for i, tt := range filterTopicsCreationFlatTests { |
||||||
|
// Check the textual creation
|
||||||
|
filter := NewFilterTopicsFromStringsFlat(tt.topics...) |
||||||
|
if len(filter) != len(tt.topics) { |
||||||
|
t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics)) |
||||||
|
continue |
||||||
|
} |
||||||
|
for j, condition := range filter { |
||||||
|
if len(condition) != len(tt.filter[j]) { |
||||||
|
t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j])) |
||||||
|
continue |
||||||
|
} |
||||||
|
for k := 0; k < len(condition); k++ { |
||||||
|
if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 { |
||||||
|
t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k]) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Check the binary creation
|
||||||
|
binary := make([][]byte, len(tt.topics)) |
||||||
|
for j, topic := range tt.topics { |
||||||
|
binary[j] = []byte(topic) |
||||||
|
} |
||||||
|
filter = NewFilterTopicsFlat(binary...) |
||||||
|
if len(filter) != len(tt.topics) { |
||||||
|
t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics)) |
||||||
|
continue |
||||||
|
} |
||||||
|
for j, condition := range filter { |
||||||
|
if len(condition) != len(tt.filter[j]) { |
||||||
|
t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j])) |
||||||
|
continue |
||||||
|
} |
||||||
|
for k := 0; k < len(condition); k++ { |
||||||
|
if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 { |
||||||
|
t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k]) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var filterCompareTests = []struct { |
||||||
|
matcher filterer |
||||||
|
message filterer |
||||||
|
match bool |
||||||
|
}{ |
||||||
|
{ // Wild-card filter matching anything
|
||||||
|
matcher: filterer{to: "", from: "", matcher: newTopicMatcher()}, |
||||||
|
message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
match: true, |
||||||
|
}, |
||||||
|
{ // Filter matching the to field
|
||||||
|
matcher: filterer{to: "to", from: "", matcher: newTopicMatcher()}, |
||||||
|
message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
match: true, |
||||||
|
}, |
||||||
|
{ // Filter rejecting the to field
|
||||||
|
matcher: filterer{to: "to", from: "", matcher: newTopicMatcher()}, |
||||||
|
message: filterer{to: "", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
match: false, |
||||||
|
}, |
||||||
|
{ // Filter matching the from field
|
||||||
|
matcher: filterer{to: "", from: "from", matcher: newTopicMatcher()}, |
||||||
|
message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
match: true, |
||||||
|
}, |
||||||
|
{ // Filter rejecting the from field
|
||||||
|
matcher: filterer{to: "", from: "from", matcher: newTopicMatcher()}, |
||||||
|
message: filterer{to: "to", from: "", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
match: false, |
||||||
|
}, |
||||||
|
{ // Filter matching the topic field
|
||||||
|
matcher: filterer{to: "", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
match: true, |
||||||
|
}, |
||||||
|
{ // Filter rejecting the topic field
|
||||||
|
matcher: filterer{to: "", from: "", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)}, |
||||||
|
message: filterer{to: "to", from: "from", matcher: newTopicMatcher()}, |
||||||
|
match: false, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestFilterCompare(t *testing.T) { |
||||||
|
for i, tt := range filterCompareTests { |
||||||
|
if match := tt.matcher.Compare(tt.message); match != tt.match { |
||||||
|
t.Errorf("test %d: match mismatch: have %v, want %v", i, match, tt.match) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
// Contains the external API side message filter for watching, pooling and polling
|
||||||
|
// matched whisper messages, also serializing data access to avoid duplications.
|
||||||
|
|
||||||
|
package xeth |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
// whisperFilter is the message cache matching a specific filter, accumulating
|
||||||
|
// inbound messages until the are requested by the client.
|
||||||
|
type whisperFilter struct { |
||||||
|
id int // Filter identifier for old message retrieval
|
||||||
|
ref *Whisper // Whisper reference for old message retrieval
|
||||||
|
|
||||||
|
cache []WhisperMessage // Cache of messages not yet polled
|
||||||
|
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.
|
||||||
|
func (w *whisperFilter) insert(messages ...WhisperMessage) { |
||||||
|
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.
|
||||||
|
func (w *whisperFilter) retrieve() (messages []WhisperMessage) { |
||||||
|
w.lock.Lock() |
||||||
|
defer w.lock.Unlock() |
||||||
|
|
||||||
|
messages, w.cache = w.cache, nil |
||||||
|
w.update = time.Now() |
||||||
|
|
||||||
|
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 |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
// Contains the external API representation of a whisper message.
|
||||||
|
|
||||||
|
package xeth |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/whisper" |
||||||
|
) |
||||||
|
|
||||||
|
// WhisperMessage is the external API representation of a whisper.Message.
|
||||||
|
type WhisperMessage struct { |
||||||
|
ref *whisper.Message |
||||||
|
|
||||||
|
Payload string `json:"payload"` |
||||||
|
To string `json:"to"` |
||||||
|
From string `json:"from"` |
||||||
|
Sent int64 `json:"sent"` |
||||||
|
TTL int64 `json:"ttl"` |
||||||
|
Hash string `json:"hash"` |
||||||
|
} |
||||||
|
|
||||||
|
// NewWhisperMessage converts an internal message into an API version.
|
||||||
|
func NewWhisperMessage(message *whisper.Message) WhisperMessage { |
||||||
|
return WhisperMessage{ |
||||||
|
ref: message, |
||||||
|
|
||||||
|
Payload: common.ToHex(message.Payload), |
||||||
|
From: common.ToHex(crypto.FromECDSAPub(message.Recover())), |
||||||
|
To: common.ToHex(crypto.FromECDSAPub(message.To)), |
||||||
|
Sent: message.Sent.Unix(), |
||||||
|
TTL: int64(message.TTL / time.Second), |
||||||
|
Hash: common.ToHex(message.Hash.Bytes()), |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue