Move ssh_requestMessages to sshext_requestMessages (#862)

* Add RequestMessage to sshext

* E2E tests now use shhext_requestMessages

* Typo in comment

* Enhanced maintainability

* Drop former mailservice

* Code reorg after review

* Fix missed changes after update to 1.8.5
This commit is contained in:
Frank Mueller 2018-04-26 07:56:19 +02:00 committed by GitHub
parent f3e2631c1d
commit b543d32a31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 237 additions and 320 deletions

View File

@ -1,128 +0,0 @@
package mailservice
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
)
const (
// defaultWorkTime is a work time reported in messages sent to MailServer nodes.
defaultWorkTime = 5
)
var (
// ErrInvalidMailServerPeer is returned when it fails to parse enode from params.
ErrInvalidMailServerPeer = errors.New("invalid mailServerPeer value")
// ErrInvalidSymKeyID is returned when it fails to get a symmetric key.
ErrInvalidSymKeyID = errors.New("invalid symKeyID value")
)
// PublicAPI defines a MailServer public API.
type PublicAPI struct {
service *MailService
log log.Logger
}
// NewPublicAPI returns a new PublicAPI.
func NewPublicAPI(s *MailService) *PublicAPI {
return &PublicAPI{
service: s,
log: log.New("package", "status-go/geth/mailservice.PublicAPI"),
}
}
// MessagesRequest is a payload send to a MailServer to get messages.
type MessagesRequest struct {
// MailServerPeer is MailServer's enode address.
MailServerPeer string `json:"mailServerPeer"`
// From is a lower bound of time range (optional).
// Default is 24 hours back from now.
From uint32 `json:"from"`
// To is a upper bound of time range (optional).
// Default is now.
To uint32 `json:"to"`
// Topic is a regular Whisper topic.
Topic whisper.TopicType `json:"topic"`
// SymKeyID is an ID of a symmetric key to authenticate to MailServer.
// It's derived from MailServer password.
SymKeyID string `json:"symKeyID"`
}
func setMessagesRequestDefaults(r *MessagesRequest) {
// set From and To defaults
if r.From == 0 && r.To == 0 {
r.From = uint32(time.Now().UTC().Add(-24 * time.Hour).Unix())
r.To = uint32(time.Now().UTC().Unix())
}
}
// RequestMessages sends a request for historic messages to a MailServer.
func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (bool, error) {
api.log.Info("RequestMessages", "request", r)
setMessagesRequestDefaults(&r)
shh := api.service.whisper
mailServerNode, err := discover.ParseNode(r.MailServerPeer)
if err != nil {
return false, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err)
}
symKey, err := shh.GetSymKey(r.SymKeyID)
if err != nil {
return false, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err)
}
envelope, err := makeEnvelop(makePayload(r), symKey, api.service.nodeID, shh.MinPow())
if err != nil {
return false, err
}
if err := shh.RequestHistoricMessages(mailServerNode.ID[:], envelope); err != nil {
return false, err
}
return true, nil
}
// makeEnvelop makes an envelop for a historic messages request.
// Symmetric key is used to authenticate to MailServer.
// PK is the current node ID.
func makeEnvelop(payload []byte, symKey []byte, nodeID *ecdsa.PrivateKey, pow float64) (*whisper.Envelope, error) {
params := whisper.MessageParams{
PoW: pow,
Payload: payload,
KeySym: symKey,
WorkTime: defaultWorkTime,
Src: nodeID,
}
message, err := whisper.NewSentMessage(&params)
if err != nil {
return nil, err
}
return message.Wrap(&params)
}
// makePayload makes a specific payload for MailServer to request historic messages.
func makePayload(r MessagesRequest) []byte {
// first 8 bytes are lowed and upper bounds as uint32
data := make([]byte, 8+whisper.BloomFilterSize)
binary.BigEndian.PutUint32(data, r.From)
binary.BigEndian.PutUint32(data[4:], r.To)
copy(data[8:], whisper.TopicToBloom(r.Topic))
return data
}

View File

@ -1,108 +0,0 @@
package mailservice
import (
"context"
"math"
"testing"
"time"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/stretchr/testify/require"
)
func TestRequestMessagesDefaults(t *testing.T) {
r := MessagesRequest{}
setMessagesRequestDefaults(&r)
require.NotZero(t, r.From)
require.InEpsilon(t, uint32(time.Now().UTC().Unix()), r.To, 1.0)
}
func TestRequestMessages(t *testing.T) {
var err error
shh := whisper.New(nil)
aNode, err := node.New(&node.Config{
P2P: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
},
}) // in-memory node as no data dir
require.NoError(t, err)
err = aNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
return shh, nil
})
require.NoError(t, err)
err = aNode.Start()
require.NoError(t, err)
defer func() {
err := aNode.Stop()
require.NoError(t, err)
}()
service := New(shh)
api := NewPublicAPI(service)
const (
mailServerPeer = "enode://b7e65e1bedc2499ee6cbd806945af5e7df0e59e4070c96821570bd581473eade24a489f5ec95d060c0db118c879403ab88d827d3766978f28708989d35474f87@[::]:51920"
)
var result bool
// invalid MailServer enode address
result, err = api.RequestMessages(context.TODO(), MessagesRequest{MailServerPeer: "invalid-address"})
require.False(t, result)
require.EqualError(t, err, "invalid mailServerPeer value: invalid URL scheme, want \"enode\"")
// non-existent symmetric key
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailServerPeer,
})
require.False(t, result)
require.EqualError(t, err, "invalid symKeyID value: non-existent key ID")
// with a symmetric key
symKeyID, symKeyErr := shh.AddSymKeyFromPassword("some-pass")
require.NoError(t, symKeyErr)
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailServerPeer,
SymKeyID: symKeyID,
})
require.Contains(t, err.Error(), "Could not find peer with ID")
require.False(t, result)
// with a peer acting as a mailserver
// prepare a node first
mailNode, err := node.New(&node.Config{
P2P: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
ListenAddr: ":0",
},
}) // in-memory node as no data dir
require.NoError(t, err)
err = mailNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
return whisper.New(nil), nil
})
require.NoError(t, err)
err = mailNode.Start()
require.NoError(t, err)
defer func() {
err := mailNode.Stop()
require.NoError(t, err)
}()
// add mailPeer as a peer
aNode.Server().AddPeer(mailNode.Server().Self())
time.Sleep(time.Second) // wait for the peer to be added
// send a request
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailNode.Server().Self().String(),
SymKeyID: symKeyID,
})
require.NoError(t, err)
require.True(t, result)
}

View File

@ -1,54 +0,0 @@
package mailservice
import (
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
)
// MailService is a service that provides some additional Whisper API.
type MailService struct {
whisper *whisper.Whisper
nodeID *ecdsa.PrivateKey
}
// Make sure that MailService implements node.Service interface.
var _ node.Service = (*MailService)(nil)
// New returns a new MailService.
func New(w *whisper.Whisper) *MailService {
return &MailService{whisper: w}
}
// Protocols returns a new protocols list. In this case, there are none.
func (s *MailService) Protocols() []p2p.Protocol {
return []p2p.Protocol{}
}
// APIs returns a list of new APIs.
func (s *MailService) APIs() []rpc.API {
return []rpc.API{
{
Namespace: "shh",
Version: "1.0",
Service: NewPublicAPI(s),
Public: true,
},
}
}
// Start is run when a service is started.
// It does nothing in this case but is required by `node.Service` interface.
func (s *MailService) Start(server *p2p.Server) error {
s.nodeID = server.PrivateKey
return nil
}
// Stop is run when a service is stopped.
// It does nothing in this case but is required by `node.Service` interface.
func (s *MailService) Stop() error {
return nil
}

View File

@ -21,7 +21,6 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/whisper/mailserver"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/geth/mailservice"
"github.com/status-im/status-go/geth/params"
shhmetrics "github.com/status-im/status-go/metrics/whisper"
"github.com/status-im/status-go/services/personal"
@ -215,7 +214,7 @@ func activateShhService(stack *node.Node, config *params.NodeConfig) (err error)
}
// TODO(dshulyak) add a config option to enable it by default, but disable if app is started from statusd
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var whisper *whisper.Whisper
if err := ctx.Service(&whisper); err != nil {
return nil, err
@ -224,18 +223,6 @@ func activateShhService(stack *node.Node, config *params.NodeConfig) (err error)
svc := shhext.New(whisper, shhext.EnvelopeSignalHandler{})
return svc, nil
})
if err != nil {
return
}
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var whisper *whisper.Whisper
if err := ctx.Service(&whisper); err != nil {
return nil, err
}
return mailservice.New(whisper), nil
})
}
// makeIPCPath returns IPC-RPC filename

View File

@ -2,26 +2,82 @@ package shhext
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
)
// NewPublicAPI returns instance of the public API.
func NewPublicAPI(w *whisper.Whisper, tracker *tracker) *PublicAPI {
return &PublicAPI{
w: w,
publicAPI: whisper.NewPublicWhisperAPI(w),
tracker: tracker,
const (
// defaultWorkTime is a work time reported in messages sent to MailServer nodes.
defaultWorkTime = 5
)
var (
// ErrInvalidMailServerPeer is returned when it fails to parse enode from params.
ErrInvalidMailServerPeer = errors.New("invalid mailServerPeer value")
// ErrInvalidSymKeyID is returned when it fails to get a symmetric key.
ErrInvalidSymKeyID = errors.New("invalid symKeyID value")
)
// -----
// PAYLOADS
// -----
// MessagesRequest is a payload send to a MailServer to get messages.
type MessagesRequest struct {
// MailServerPeer is MailServer's enode address.
MailServerPeer string `json:"mailServerPeer"`
// From is a lower bound of time range (optional).
// Default is 24 hours back from now.
From uint32 `json:"from"`
// To is a upper bound of time range (optional).
// Default is now.
To uint32 `json:"to"`
// Topic is a regular Whisper topic.
Topic whisper.TopicType `json:"topic"`
// SymKeyID is an ID of a symmetric key to authenticate to MailServer.
// It's derived from MailServer password.
SymKeyID string `json:"symKeyID"`
}
func (r *MessagesRequest) setDefaults() {
// set From and To defaults
if r.From == 0 && r.To == 0 {
r.From = uint32(time.Now().UTC().Add(-24 * time.Hour).Unix())
r.To = uint32(time.Now().UTC().Unix())
}
}
// -----
// PUBLIC API
// -----
// PublicAPI extends whisper public API.
type PublicAPI struct {
w *whisper.Whisper
service *Service
publicAPI *whisper.PublicWhisperAPI
tracker *tracker
log log.Logger
}
// NewPublicAPI returns instance of the public API.
func NewPublicAPI(s *Service) *PublicAPI {
return &PublicAPI{
service: s,
publicAPI: whisper.NewPublicWhisperAPI(s.w),
log: log.New("package", "status-go/services/sshext.PublicAPI"),
}
}
// Post shamelessly copied from whisper codebase with slight modifications.
@ -30,7 +86,70 @@ func (api *PublicAPI) Post(ctx context.Context, req whisper.NewMessage) (hash he
if err == nil {
var envHash common.Hash
copy(envHash[:], hash[:]) // slice can't be used as key
api.tracker.Add(envHash)
api.service.tracker.Add(envHash)
}
return hash, err
}
// RequestMessages sends a request for historic messages to a MailServer.
func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (bool, error) {
api.log.Info("RequestMessages", "request", r)
r.setDefaults()
shh := api.service.w
mailServerNode, err := discover.ParseNode(r.MailServerPeer)
if err != nil {
return false, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err)
}
symKey, err := shh.GetSymKey(r.SymKeyID)
if err != nil {
return false, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err)
}
envelope, err := makeEnvelop(makePayload(r), symKey, api.service.nodeID, shh.MinPow())
if err != nil {
return false, err
}
if err := shh.RequestHistoricMessages(mailServerNode.ID[:], envelope); err != nil {
return false, err
}
return true, nil
}
// -----
// HELPER
// -----
// makeEnvelop makes an envelop for a historic messages request.
// Symmetric key is used to authenticate to MailServer.
// PK is the current node ID.
func makeEnvelop(payload []byte, symKey []byte, nodeID *ecdsa.PrivateKey, pow float64) (*whisper.Envelope, error) {
params := whisper.MessageParams{
PoW: pow,
Payload: payload,
KeySym: symKey,
WorkTime: defaultWorkTime,
Src: nodeID,
}
message, err := whisper.NewSentMessage(&params)
if err != nil {
return nil, err
}
return message.Wrap(&params)
}
// makePayload makes a specific payload for MailServer to request historic messages.
func makePayload(r MessagesRequest) []byte {
// first 8 bytes are lowed and upper bounds as uint32
data := make([]byte, 8+whisper.BloomFilterSize)
binary.BigEndian.PutUint32(data, r.From)
binary.BigEndian.PutUint32(data[4:], r.To)
copy(data[8:], whisper.TopicToBloom(r.Topic))
return data
}

View File

@ -1,6 +1,7 @@
package shhext
import (
"crypto/ecdsa"
"sync"
"github.com/ethereum/go-ethereum/common"
@ -31,6 +32,7 @@ type EnvelopeEventsHandler interface {
type Service struct {
w *whisper.Whisper
tracker *tracker
nodeID *ecdsa.PrivateKey
}
// Make sure that Service implements node.Service interface.
@ -60,7 +62,7 @@ func (s *Service) APIs() []rpc.API {
{
Namespace: "shhext",
Version: "1.0",
Service: NewPublicAPI(s.w, s.tracker),
Service: NewPublicAPI(s),
Public: true,
},
}
@ -70,6 +72,7 @@ func (s *Service) APIs() []rpc.API {
// It does nothing in this case but is required by `node.Service` interface.
func (s *Service) Start(server *p2p.Server) error {
s.tracker.Start()
s.nodeID = server.PrivateKey
return nil
}

View File

@ -1,7 +1,9 @@
package shhext
import (
"context"
"fmt"
"math"
"testing"
"time"
@ -126,6 +128,102 @@ func (s *ShhExtSuite) TestWaitMessageExpired() {
}
}
func (s *ShhExtSuite) TestRequestMessagesDefaults() {
r := MessagesRequest{}
r.setDefaults()
s.NotZero(r.From)
s.InEpsilon(uint32(time.Now().UTC().Unix()), r.To, 1.0)
}
func (s *ShhExtSuite) TestRequestMessages() {
var err error
shh := whisper.New(nil)
aNode, err := node.New(&node.Config{
P2P: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
},
}) // in-memory node as no data dir
s.NoError(err)
err = aNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
return shh, nil
})
s.NoError(err)
err = aNode.Start()
s.NoError(err)
defer func() {
err := aNode.Stop()
s.NoError(err)
}()
mock := newHandlerMock(1)
service := New(shh, mock)
api := NewPublicAPI(service)
const (
mailServerPeer = "enode://b7e65e1bedc2499ee6cbd806945af5e7df0e59e4070c96821570bd581473eade24a489f5ec95d060c0db118c879403ab88d827d3766978f28708989d35474f87@[::]:51920"
)
var result bool
// invalid MailServer enode address
result, err = api.RequestMessages(context.TODO(), MessagesRequest{MailServerPeer: "invalid-address"})
s.False(result)
s.EqualError(err, "invalid mailServerPeer value: invalid URL scheme, want \"enode\"")
// non-existent symmetric key
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailServerPeer,
})
s.False(result)
s.EqualError(err, "invalid symKeyID value: non-existent key ID")
// with a symmetric key
symKeyID, symKeyErr := shh.AddSymKeyFromPassword("some-pass")
s.NoError(symKeyErr)
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailServerPeer,
SymKeyID: symKeyID,
})
s.Contains(err.Error(), "Could not find peer with ID")
s.False(result)
// with a peer acting as a mailserver
// prepare a node first
mailNode, err := node.New(&node.Config{
P2P: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
ListenAddr: ":0",
},
}) // in-memory node as no data dir
s.NoError(err)
err = mailNode.Register(func(_ *node.ServiceContext) (node.Service, error) {
return whisper.New(nil), nil
})
s.NoError(err)
err = mailNode.Start()
s.NoError(err)
defer func() {
err := mailNode.Stop()
s.NoError(err)
}()
// add mailPeer as a peer
aNode.Server().AddPeer(mailNode.Server().Self())
time.Sleep(time.Second) // wait for the peer to be added
// send a request
result, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailNode.Server().Self().String(),
SymKeyID: symKeyID,
})
s.NoError(err)
s.True(result)
}
func (s *ShhExtSuite) TearDown() {
for _, n := range s.nodes {
s.NoError(n.Stop())

View File

@ -24,9 +24,9 @@ func (s *MailServiceSuite) SetupTest() {
s.StatusNode = node.New()
}
// TestShhRequestMessagesRPCMethodAvailability tests if `shh_requestMessages` is available
// TestShhextRequestMessagesRPCMethodAvailability tests if `shhext_requestMessages` is available
// through inproc and HTTP interfaces.
func (s *MailServiceSuite) TestShhRequestMessagesRPCMethodAvailability() {
func (s *MailServiceSuite) TestShhextRequestMessagesRPCMethodAvailability() {
r := s.Require()
s.StartTestNode(func(config *params.NodeConfig) {
@ -39,14 +39,14 @@ func (s *MailServiceSuite) TestShhRequestMessagesRPCMethodAvailability() {
// This error means that the method is available through inproc communication
// as the validation of params occurred.
err := client.Call(nil, "shh_requestMessages", map[string]interface{}{})
err := client.Call(nil, "shhext_requestMessages", map[string]interface{}{})
r.EqualError(err, `invalid mailServerPeer value: invalid URL scheme, want "enode"`)
// Do the same but using HTTP interface.
req, err := http.NewRequest("POST", "http://localhost:8645", bytes.NewBuffer([]byte(`{
"jsonrpc": "2.0",
"id": 1,
"method": "shh_requestMessages",
"method": "shhext_requestMessages",
"params": [{}]
}`)))
req.Header.Set("Content-Type", "application/json")

View File

@ -98,7 +98,7 @@ func (s *WhisperMailboxSuite) TestRequestMessageFromMailboxAsync() {
reqMessagesBody := `{
"jsonrpc": "2.0",
"id": 1,
"method": "shh_requestMessages",
"method": "shhext_requestMessages",
"params": [{
"mailServerPeer":"` + mailboxPeerStr + `",
"topic":"` + topic.String() + `",
@ -479,7 +479,7 @@ func (s *WhisperMailboxSuite) requestHistoricMessages(rpcCli *rpc.Client, mailbo
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"id": 2,
"method": "shh_requestMessages",
"method": "shhext_requestMessages",
"params": [{
"mailServerPeer":"` + mailboxEnode + `",
"topic":"` + topic + `",