New debug_postSync endpoint (#1046)

* [#ISSUE-1041] New debug_postconfirm endpoint

* move debug service api inside shhext service
This commit is contained in:
Adrià Cidre 2018-06-25 15:27:17 +02:00 committed by GitHub
parent 3b052098fe
commit 5bbfabde94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 370 additions and 20 deletions

View File

@ -16,8 +16,8 @@ define NOT_IN_GOPATH_ERROR
Current dir is $(CURDIR), which seems to be different from your GOPATH.
Please, build status-go from GOPATH for proper build.
GOPATH = $(shell go env GOPATH)
Current dir = $(CURDIR)
GOPATH = $(shell go env GOPATH)
Current dir = $(CURDIR)
Expected dir = $(EXPECTED_PATH))
See https://golang.org/doc/code.html#GOPATH for more info
@ -161,7 +161,7 @@ mock: ##@other Regenerate mocks
mockgen -package=fake -destination=transactions/fake/mock.go -source=transactions/fake/txservice.go
mockgen -package=account -destination=account/accounts_mock.go -source=account/accounts.go
mockgen -package=jail -destination=jail/cell_mock.go -source=jail/cell.go
mockgen -package=status -destination=services/status/account_mock.go -source=services/status/service.go
mockgen -package=status -destination=services/status/account_mock.go -source=services/status/service.go
docker-test: ##@tests Run tests in a docker container with golang.
docker run --privileged --rm -it -v "$(shell pwd):$(DOCKER_TEST_WORKDIR)" -w "$(DOCKER_TEST_WORKDIR)" $(DOCKER_TEST_IMAGE) go test ${ARGS}

View File

@ -41,6 +41,7 @@ var (
lesEnabled = flag.Bool("les", false, "Enable LES protocol")
whisperEnabled = flag.Bool("shh", false, "Enable Whisper protocol")
statusService = flag.String("status", "", `Enable StatusService, possible values: "ipc", "http"`)
debugAPI = flag.Bool("debug", false, `Enable debug API endpoints under "debug_" namespace`)
swarmEnabled = flag.Bool("swarm", false, "Enable Swarm protocol")
maxPeers = flag.Int("maxpeers", 25, "maximum number of p2p peers (including all protocols)")
httpEnabled = flag.Bool("http", false, "Enable HTTP RPC endpoint")
@ -259,11 +260,15 @@ func makeNodeConfig() (*params.NodeConfig, error) {
nodeConfig.ClusterConfig.BootNodes = strings.Split(*bootnodes, ",")
}
nodeConfig, err = configureStatusService(*statusService, nodeConfig)
if err != nil {
if nodeConfig, err = configureStatusService(*statusService, nodeConfig); err != nil {
return nil, err
}
nodeConfig.DebugAPIEnabled = *debugAPI
if nodeConfig.DebugAPIEnabled {
nodeConfig.AddAPIModule("debug")
}
if *whisperEnabled {
return whisperConfig(nodeConfig)
}

View File

@ -259,7 +259,7 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb
return nil, err
}
svc := shhext.New(whisper, shhext.EnvelopeSignalHandler{}, db)
svc := shhext.New(whisper, shhext.EnvelopeSignalHandler{}, db, config.DebugAPIEnabled)
return svc, nil
})
}

View File

@ -318,6 +318,9 @@ type NodeConfig struct {
// StatusServiceEnabled enables status service api
StatusServiceEnabled bool
// DebugAPIEnabled enables debug api
DebugAPIEnabled bool
}
// NewNodeConfig creates new node configuration object

74
services/shhext/debug.go Normal file
View File

@ -0,0 +1,74 @@
package shhext
import (
"context"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/services"
)
var (
postSyncTimeout = 60 * time.Second
errEnvelopeExpired = errors.New("envelope expired before being sent")
errNoShhextAttachedAPI = errors.New("No shhext attached")
)
// DebugAPI represents a set of APIs from the `web3.debug` namespace.
type DebugAPI struct {
s *Service
}
// NewDebugAPI creates an instance of the debug API.
func NewDebugAPI(s *Service) *DebugAPI {
return &DebugAPI{s: s}
}
// PostSync sends an envelope through shhext_post and waits until it's sent.
func (api *DebugAPI) PostSync(ctx context.Context, req whisper.NewMessage) (hash hexutil.Bytes, err error) {
shhAPI := services.APIByNamespace(api.s.APIs(), "shhext")
if shhAPI == nil {
err = errNoShhextAttachedAPI
return
}
s, ok := shhAPI.(*PublicAPI)
if !ok {
err = errNoShhextAttachedAPI
return
}
hash, err = s.Post(ctx, req)
if err != nil {
return
}
ctxTimeout, cancel := context.WithTimeout(ctx, postSyncTimeout)
defer cancel()
err = api.waitForHash(ctxTimeout, hash)
return
}
// waitForHash waits for a specific hash to be sent
func (api *DebugAPI) waitForHash(ctx context.Context, hash hexutil.Bytes) error {
h := common.BytesToHash(hash)
events := make(chan whisper.EnvelopeEvent, 100)
sub := api.s.w.SubscribeEnvelopeEvents(events)
defer sub.Unsubscribe()
for {
select {
case ev := <-events:
if ev.Hash == h {
if ev.Event == whisper.EventEnvelopeSent {
return nil
}
if ev.Event == whisper.EventEnvelopeExpired {
return errEnvelopeExpired
}
}
case <-ctx.Done():
return fmt.Errorf("wait for hash canceled: %v", ctx.Err())
}
}
}

View File

@ -41,13 +41,14 @@ type Service struct {
tracker *tracker
nodeID *ecdsa.PrivateKey
deduplicator *dedup.Deduplicator
debug bool
}
// Make sure that Service implements node.Service interface.
var _ node.Service = (*Service)(nil)
// New returns a new Service.
func New(w *whisper.Whisper, handler EnvelopeEventsHandler, db *leveldb.DB) *Service {
func New(w *whisper.Whisper, handler EnvelopeEventsHandler, db *leveldb.DB, debug bool) *Service {
track := &tracker{
w: w,
handler: handler,
@ -57,6 +58,7 @@ func New(w *whisper.Whisper, handler EnvelopeEventsHandler, db *leveldb.DB) *Ser
w: w,
tracker: track,
deduplicator: dedup.NewDeduplicator(w, db),
debug: debug,
}
}
@ -67,7 +69,7 @@ func (s *Service) Protocols() []p2p.Protocol {
// APIs returns a list of new APIs.
func (s *Service) APIs() []rpc.API {
return []rpc.API{
apis := []rpc.API{
{
Namespace: "shhext",
Version: "1.0",
@ -75,6 +77,17 @@ func (s *Service) APIs() []rpc.API {
Public: true,
},
}
if s.debug {
apis = append(apis, rpc.API{
Namespace: "debug",
Version: "1.0",
Service: NewDebugAPI(s),
Public: true,
})
}
return apis
}
// Start is run when a service is started.

View File

@ -2,6 +2,7 @@ package shhext
import (
"context"
"errors"
"fmt"
"math"
"testing"
@ -78,7 +79,7 @@ func (s *ShhExtSuite) SetupTest() {
s.NoError(stack.Register(func(n *node.ServiceContext) (node.Service, error) {
return s.whisper[i], nil
}))
s.services[i] = New(s.whisper[i], nil, nil)
s.services[i] = New(s.whisper[i], nil, nil, true)
s.NoError(stack.Register(func(n *node.ServiceContext) (node.Service, error) {
return s.services[i], nil
}))
@ -164,7 +165,7 @@ func (s *ShhExtSuite) TestRequestMessages() {
}()
mock := newHandlerMock(1)
service := New(shh, mock, nil)
service := New(shh, mock, nil, false)
api := NewPublicAPI(service)
const (
@ -231,6 +232,98 @@ func (s *ShhExtSuite) TestRequestMessages() {
s.Contains(api.service.tracker.cache, common.BytesToHash(hash))
}
func (s *ShhExtSuite) TestDebugPostSync() {
mock := newHandlerMock(1)
s.services[0].tracker.handler = mock
symID, err := s.whisper[0].GenerateSymKey()
s.NoError(err)
s.nodes[0].Server().AddPeer(s.nodes[1].Server().Self())
client, err := s.nodes[0].Attach()
s.NoError(err)
var hash common.Hash
var testCases = []struct {
name string
msg whisper.NewMessage
postSyncTimeout time.Duration
expectedErr error
}{
{
name: "timeout",
msg: whisper.NewMessage{
SymKeyID: symID,
PowTarget: whisper.DefaultMinimumPoW,
PowTime: 200,
Topic: whisper.TopicType{0x01, 0x01, 0x01, 0x01},
Payload: []byte("hello"),
},
postSyncTimeout: postSyncTimeout,
expectedErr: nil,
},
{
name: "invalid message",
msg: whisper.NewMessage{
PowTarget: whisper.DefaultMinimumPoW,
PowTime: 200,
Topic: whisper.TopicType{0x01, 0x01, 0x01, 0x01},
Payload: []byte("hello"),
},
postSyncTimeout: postSyncTimeout,
expectedErr: whisper.ErrSymAsym,
},
{
name: "context deadline exceeded",
msg: whisper.NewMessage{
SymKeyID: symID,
PowTarget: whisper.DefaultMinimumPoW,
PowTime: 10,
Topic: whisper.TopicType{0x01, 0x01, 0x01, 0x01},
TTL: 100,
Payload: []byte("hello"),
},
postSyncTimeout: 1 * time.Millisecond,
expectedErr: errors.New("context deadline exceeded"),
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), tc.postSyncTimeout)
defer cancel()
err := client.CallContext(ctx, &hash, "debug_postSync", tc.msg)
if tc.expectedErr != nil {
s.Equal(tc.expectedErr.Error(), err.Error())
} else {
s.NoError(err)
}
})
}
}
func (s *ShhExtSuite) TestEnvelopeExpiredOnDebugPostSync() {
mock := newHandlerMock(1)
s.services[0].tracker.handler = mock
symID, err := s.whisper[0].GenerateSymKey()
s.NoError(err)
client, err := s.nodes[0].Attach()
s.NoError(err)
var hash common.Hash
ctx, cancel := context.WithTimeout(context.Background(), postSyncTimeout)
defer cancel()
err = client.CallContext(ctx, &hash, "debug_postSync", whisper.NewMessage{
SymKeyID: symID,
PowTarget: whisper.DefaultMinimumPoW,
PowTime: 200,
Topic: whisper.TopicType{0x01, 0x01, 0x01, 0x01},
Payload: []byte("hello"),
TTL: 1,
})
s.Equal(errEnvelopeExpired.Error(), err.Error())
}
func (s *ShhExtSuite) TearDown() {
for _, n := range s.nodes {
s.NoError(n.Stop())

View File

@ -25,7 +25,7 @@ type AccountManager interface {
CreateAccount(password string) (address, pubKey, mnemonic string, err error)
}
// Service represents out own implementation of status status operations.
// Service represents our own implementation of status status operations.
type Service struct {
am AccountManager
w WhisperService

13
services/utils.go Normal file
View File

@ -0,0 +1,13 @@
package services
import "github.com/ethereum/go-ethereum/rpc"
// APIByNamespace retrieve an api by its namespace or returns nil.
func APIByNamespace(apis []rpc.API, namespace string) interface{} {
for _, api := range apis {
if api.Namespace == namespace {
return api.Service
}
}
return nil
}

View File

@ -35,7 +35,7 @@ func testMailserverPeer(t *testing.T) {
shhService := createWhisperService()
shhAPI := whisper.NewPublicWhisperAPI(shhService)
mailService := shhext.New(shhService, nil, nil)
mailService := shhext.New(shhService, nil, nil, false)
shhextAPI := shhext.NewPublicAPI(mailService)
// create node with services

View File

@ -56,7 +56,7 @@ func (s *BaseJSONRPCSuite) isMethodExported(method string, private bool) bool {
return !(response.Error != nil && response.Error.Code == methodNotFoundErrorCode)
}
func (s *BaseJSONRPCSuite) SetupTest(upstreamEnabled bool, statusServiceEnabled bool) error {
func (s *BaseJSONRPCSuite) SetupTest(upstreamEnabled, statusServiceEnabled, debugAPIEnabled bool) error {
s.Backend = api.NewStatusBackend()
s.NotNil(s.Backend)
@ -65,6 +65,10 @@ func (s *BaseJSONRPCSuite) SetupTest(upstreamEnabled bool, statusServiceEnabled
nodeConfig.IPCEnabled = false
nodeConfig.StatusServiceEnabled = statusServiceEnabled
nodeConfig.DebugAPIEnabled = debugAPIEnabled
if nodeConfig.DebugAPIEnabled {
nodeConfig.AddAPIModule("debug")
}
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
if upstreamEnabled {

View File

@ -0,0 +1,145 @@
package services
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/node"
"github.com/status-im/status-go/params"
"github.com/stretchr/testify/suite"
. "github.com/status-im/status-go/t/utils"
)
func TestDebugAPISuite(t *testing.T) {
s := new(DebugAPISuite)
s.upstream = false
suite.Run(t, s)
}
func TestDebugAPISuiteUpstream(t *testing.T) {
s := new(DebugAPISuite)
s.upstream = true
suite.Run(t, s)
}
type DebugAPISuite struct {
BaseJSONRPCSuite
upstream bool
}
func (s *DebugAPISuite) TestAccessibleDebugAPIsUnexported() {
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
s.T().Skip()
return
}
err := s.SetupTest(s.upstream, false, false)
s.NoError(err)
// Debug APIs should be unavailable
s.AssertAPIMethodUnexported("debug_postSync")
err = s.Backend.StopNode()
s.NoError(err)
err = s.SetupTest(s.upstream, false, true)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
s.NoError(err)
}()
// Debug APIs should be available
s.AssertAPIMethodExported("debug_postSync")
}
func (s *DebugAPISuite) TestDebugPostSyncSuccess() {
// Test upstream if that's not StatusChain
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
s.T().Skip()
return
}
err := s.SetupTest(s.upstream, false, true)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
s.NoError(err)
}()
dir, err := ioutil.TempDir("", "test-debug")
s.NoError(err)
defer os.RemoveAll(dir) //nolint: errcheck
s.addPeerToCurrentNode(dir)
symID := s.generateSymKey()
result := s.sendPostConfirmMessage(symID)
var r struct {
Error struct {
Message string `json:"message"`
} `json:"error"`
Result hexutil.Bytes `json:"result"`
}
s.NoError(json.Unmarshal([]byte(result), &r))
s.Empty(r.Error.Message)
s.NotEmpty(r.Result)
}
// generateSymKey generates and stores a symetric key.
func (s *DebugAPISuite) generateSymKey() string {
w, err := s.Backend.StatusNode().WhisperService()
s.Require().NoError(err)
symID, err := w.GenerateSymKey()
s.Require().NoError(err)
return symID
}
// sendPostConfirmMessage calls debug_postSync endpoint with valid
// parameters.
func (s *DebugAPISuite) sendPostConfirmMessage(symID string) string {
req := whisper.NewMessage{
SymKeyID: symID,
PowTarget: whisper.DefaultMinimumPoW,
PowTime: 200,
Topic: whisper.TopicType{0x01, 0x01, 0x01, 0x01},
Payload: []byte("hello"),
}
body, err := json.Marshal(req)
s.NoError(err)
basicCall := fmt.Sprintf(
`{"jsonrpc":"2.0","method":"debug_postSync","params":[%s],"id":67}`,
body)
return s.Backend.CallPrivateRPC(basicCall)
}
// addPeers adds a peer to the running node
func (s *DebugAPISuite) addPeerToCurrentNode(dir string) {
s.Require().NotNil(s.Backend)
node1 := s.Backend.StatusNode().GethNode()
s.NotNil(node1)
node2 := s.newPeer("test2", dir).GethNode()
s.NotNil(node2)
node1.Server().AddPeer(node2.Server().Self())
}
// newNode creates, configures and starts a new peer.
func (s *DebugAPISuite) newPeer(name, dir string) *node.StatusNode {
// network id is irrelevant
cfg, err := params.NewNodeConfig(dir, "", 777)
cfg.LightEthConfig.Enabled = false
cfg.Name = name
cfg.NetworkID = uint64(GetNetworkID())
s.Require().NoError(err)
n := node.New()
s.Require().NoError(n.Start(cfg))
return n
}

View File

@ -36,7 +36,7 @@ type FiltersAPISuite struct {
}
func (s *FiltersAPISuite) TestFilters() {
err := s.SetupTest(s.upstream, false)
err := s.SetupTest(s.upstream, false, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()

View File

@ -61,7 +61,7 @@ func (s *PersonalSignSuite) TestRestrictedPersonalAPIs() {
return
}
err := s.SetupTest(s.upstream, false)
err := s.SetupTest(s.upstream, false, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
@ -191,7 +191,7 @@ func (s *PersonalSignSuite) testPersonalSign(testParams testParams) string {
testParams.HandlerFactory = s.notificationHandlerSuccess
}
err := s.SetupTest(s.upstream, false)
err := s.SetupTest(s.upstream, false, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
@ -252,7 +252,7 @@ func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
return
}
err := s.SetupTest(s.upstream, false)
err := s.SetupTest(s.upstream, false, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()

View File

@ -46,7 +46,7 @@ func (s *StatusAPISuite) TestAccessibleStatusAPIs() {
return
}
err := s.SetupTest(s.upstream, true)
err := s.SetupTest(s.upstream, true, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
@ -113,7 +113,7 @@ func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.Lo
testParams.HandlerFactory = s.notificationHandlerSuccess
}
err := s.SetupTest(s.upstream, true)
err := s.SetupTest(s.upstream, true, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
@ -159,7 +159,7 @@ func (s *StatusAPISuite) testStatusSignup(testParams statusTestParams) *status.S
testParams.HandlerFactory = s.notificationHandlerSuccess
}
err := s.SetupTest(s.upstream, true)
err := s.SetupTest(s.upstream, true, false)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()