Skip to content
This repository has been archived by the owner on Dec 20, 2023. It is now read-only.

Add RPC client #103

Merged
merged 7 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions chain/consensus/mir/rpc/rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package rpc
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved

import (
"bytes"
"context"
"fmt"
"net/http"

rpc "github.com/gorilla/rpc/v2/json2"
)

type JSONRPCRequestSender interface {
SendRequest(method string, params interface{}, reply interface{}) error
}

var _ JSONRPCRequestSender = &JSONRPCClient{}

type JSONRPCClient struct {
ctx context.Context
token string
url string
}

func NewJSONRPCClient(url, token string) *JSONRPCClient {
return &JSONRPCClient{
ctx: context.Background(),
token: token,
url: url,
}
}

// NewInsecureJSONRPCClient creates a JSON RPC client with empty credentials.
func NewInsecureJSONRPCClient(url string) *JSONRPCClient {
return &JSONRPCClient{
ctx: context.Background(),
token: "",
url: url,
}
}

func NewJSONRPCClientWithContext(ctx context.Context, url, token string) *JSONRPCClient {
return &JSONRPCClient{
ctx: ctx,
token: token,
url: url,
}
}

func (c *JSONRPCClient) SendRequest(method string, params interface{}, reply interface{}) error {
paramBytes, err := rpc.EncodeClientRequest(method, params)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that the method an parameters generated from this package are compatible with our implementation in the IPC agent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I asked @cryptoAtwill to create a string-based test in the JSON RPC server repo. Then I can use the same strings (serialized requests) to test them here.
Do you have other ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for now, just to double-check before we get integration tests ready.

if err != nil {
return fmt.Errorf("failed to encode client params: %w", err)
}

req, err := http.NewRequestWithContext(
c.ctx,
"POST",
c.url,
bytes.NewBuffer(paramBytes),
)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if c.token != "" {
req.Header.Add("Authorization", "Bearer "+c.token)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to issue request: %w", err)
}

// Return an error for any nonsuccessful status code
if resp.StatusCode < 200 || resp.StatusCode > 299 {
// Drop any error during close to report the original error
_ = resp.Body.Close()
return fmt.Errorf("received status code: %d", resp.StatusCode)
}

if err := rpc.DecodeClientResponse(resp.Body, reply); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, we should ensure that is compatible with our implementation.

// Drop any error during close to report the original error
_ = resp.Body.Close()
return fmt.Errorf("failed to decode client response: %w", err)
}
return resp.Body.Close()
}
53 changes: 53 additions & 0 deletions chain/consensus/mir/rpc/rpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package rpc

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/rpc/v2"
"github.com/gorilla/rpc/v2/json2"
"github.com/stretchr/testify/require"
)

type TestService struct{}

type TestServiceRequest struct {
A int
B int
}

type TestServiceResponse struct {
Result int
}

func (t *TestService) Multiply(r *http.Request, req *TestServiceRequest, res *TestServiceResponse) error {
res.Result = req.A * req.B
return nil
}

func TestJSONRPCClient(t *testing.T) {
s := rpc.NewServer()
s.RegisterCodec(json2.NewCodec(), "application/json")
err := s.RegisterService(new(TestService), "")
require.NoError(t, err)
require.Equal(t, true, s.HasMethod("TestService.Multiply"))

token := "token123"

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "POST", r.Method)
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
require.Equal(t, "Bearer "+token, r.Header.Get("Authorization"))

s.ServeHTTP(w, r)
}))
defer srv.Close()

c := NewJSONRPCClient(srv.URL, token)

var resp *TestServiceResponse
err = c.SendRequest("TestService.Multiply", &TestServiceRequest{A: 1, B: 4}, &resp)
require.NoError(t, err)
require.Equal(t, 4, resp.Result)
}
28 changes: 28 additions & 0 deletions chain/consensus/mir/validator/membership.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package validator

import (
"github.com/filecoin-project/lotus/chain/consensus/mir/rpc"
)

type FileMembership struct {
FileName string
}
Expand Down Expand Up @@ -32,3 +36,27 @@ type EnvMembership string
func (e EnvMembership) GetValidatorSet() (*Set, error) {
return NewValidatorSetFromEnv(string(e))
}

// -----

const JSONRPCServerURL = "http://127.0.0.1:3030"
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved

type ActorMembership struct {
client rpc.JSONRPCRequestSender
}

func NewActorMembershipClient(client rpc.JSONRPCRequestSender) *ActorMembership {
return &ActorMembership{
client: client,
}
}

// GetValidatorSet gets the membership config from the actor state.
func (c *ActorMembership) GetValidatorSet() (*Set, error) {
var set Set
err := c.client.SendRequest("Filecoin.GetValidatorSet", nil, &set)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if err != nil {
return nil, err
}
return &set, err
}
1 change: 0 additions & 1 deletion chain/consensus/mir/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type Validator struct {
}

// NewValidatorFromString parses a validator address from the string.
// OpaqueNetAddr can contain GRPC or libp2p addresses.
//
// Examples of validator strings:
// - t1wpixt5mihkj75lfhrnaa6v56n27epvlgwparujy@/ip4/127.0.0.1/tcp/10000/p2p/12D3KooWJhKBXvytYgPCAaiRtiNLJNSFG5jreKDu2jiVpJetzvVJ
Expand Down
38 changes: 26 additions & 12 deletions cmd/eudico/mirvalidator/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/consensus/mir/validator"

"github.com/filecoin-project/mir/pkg/checkpoint"
mirlibp2p "github.com/filecoin-project/mir/pkg/net/libp2p"
t "github.com/filecoin-project/mir/pkg/types"
Expand All @@ -23,6 +23,7 @@ import (
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/consensus/mir"
mirkv "github.com/filecoin-project/lotus/chain/consensus/mir/db/kv"
"github.com/filecoin-project/lotus/chain/consensus/mir/validator"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/eudico-core/global"
"github.com/filecoin-project/lotus/lib/ulimit"
Expand Down Expand Up @@ -60,6 +61,16 @@ var runCmd = &cli.Command{
Name: "init-checkpoint",
Usage: "pass initial checkpoint as a file (it overwrites 'init-height' flag)",
},
&cli.StringFlag{
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
Name: "membership",
Usage: "membership type: onchain, file",
Value: "file",
},
&cli.StringFlag{
Name: "membership-file",
Usage: "membership type: onchain, file",
Value: MembershipCfgPath,
},
&cli.StringFlag{
Name: "restore-configuration-number",
Usage: "use persisted configuration number",
Expand Down Expand Up @@ -128,10 +139,6 @@ var runCmd = &cli.Command{
return err
}

// Membership config.
// TODO: Make this configurable.
membershipFile := filepath.Join(cctx.String("repo"), MembershipCfgPath)

// Segment length period.
segmentLength := cctx.Int("segment-length")

Expand Down Expand Up @@ -168,7 +175,14 @@ var runCmd = &cli.Command{
log.Info("Initializing mir validator from checkpoint in height: %d", cctx.Int("init-height"))
}

membership := validator.NewFileMembership(membershipFile)
var membership validator.Reader
switch cctx.String("membership") {
case "file":
mf := filepath.Join(cctx.String("repo"), cctx.String("membership-file"))
membership = validator.NewFileMembership(mf)
default:
return xerrors.Errorf("membership is currently only supported with file")
}

var netLogger = mir.NewLogger(validatorID.String())
netTransport := mirlibp2p.NewTransport(mirlibp2p.DefaultParams(), t.NodeID(validatorID.String()), h, netLogger)
Expand All @@ -185,25 +199,25 @@ var runCmd = &cli.Command{

func validatorIDFromFlag(ctx context.Context, cctx *cli.Context, nodeApi api.FullNode) (address.Address, error) {
var (
validator address.Address
err error
addr address.Address
err error
)

if cctx.Bool("default-key") {
validator, err = nodeApi.WalletDefaultAddress(ctx)
addr, err = nodeApi.WalletDefaultAddress(ctx)
if err != nil {
return address.Undef, err
}
}
if cctx.String("from") != "" {
validator, err = address.NewFromString(cctx.String("from"))
addr, err = address.NewFromString(cctx.String("from"))
if err != nil {
return address.Undef, err
}
}
if validator == address.Undef {
if addr == address.Undef {
return address.Undef, xerrors.Errorf("no validator address specified as first argument for validator")
}

return validator, nil
return addr, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ require (
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/rpc v1.2.0
github.com/gorilla/websocket v1.5.0
github.com/gregdhill/go-openrpc v0.0.0-20220114144539-ae6f44720487
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
Expand Down
34 changes: 34 additions & 0 deletions itests/kit/mir_rpc_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package kit

import (
"encoding/json"

"github.com/filecoin-project/lotus/chain/consensus/mir/rpc"
"github.com/filecoin-project/lotus/chain/consensus/mir/validator"
)

var _ rpc.JSONRPCRequestSender = &StubJSONRPCClient{}

type StubJSONRPCClient struct {
nextSet string
}

func NewStubJSONRPCClient() *StubJSONRPCClient {
return &StubJSONRPCClient{}
}

func (c *StubJSONRPCClient) SendRequest(method string, params interface{}, reply interface{}) error {
set, err := validator.NewValidatorSetFromString(c.nextSet)
if err != nil {
return err
}
b, err := json.Marshal(set)
if err != nil {
return err
}
err = json.Unmarshal(b, reply)
if err != nil {
return err
}
return nil
}
11 changes: 8 additions & 3 deletions itests/kit/mir_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (
)

const (
FakeMembership = 0
StringMembership = 1
FileMembership = 2
FakeMembership = 0
StringMembership = 1
FileMembership = 2
OnChainMembership = 3
)

type MirConfig struct {
Expand Down Expand Up @@ -84,6 +85,10 @@ func NewMirValidator(t *testing.T, miner *TestValidator, db *TestDB, cfg *MirCon
return nil, fmt.Errorf("membership file is not specified")
}
v.membership = validator.FileMembership{FileName: cfg.MembershipFileName}
case OnChainMembership:
cl := NewStubJSONRPCClient()
cl.nextSet = cfg.MembershipString
v.membership = validator.NewActorMembershipClient(cl)
default:
return nil, fmt.Errorf("unknown membership type")
}
Expand Down
26 changes: 26 additions & 0 deletions itests/mir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,32 @@ func TestMirReconfiguration_AddAndRemoveOneValidator(t *testing.T) {
}
}

// TestMirReconfigurationOnChain_RunSubnet tests that the membership can be received using a stub JSON RPC client.
func TestMirReconfigurationOnChain_RunSubnetWithStubJSONRPC(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)

defer func() {
t.Logf("[*] defer: cancelling %s context", t.Name())
cancel()
err := g.Wait()
require.NoError(t, err)
t.Logf("[*] defer: system %s stopped", t.Name())
}()

nodes, validators, ens := kit.EnsembleWithMirValidators(t, MirTotalValidatorNumber+1)

ens.InterconnectFullNodes().BeginMirMiningWithConfig(ctx, g, validators[:MirTotalValidatorNumber],
&kit.MirConfig{
MembershipType: kit.OnChainMembership,
})

err := kit.AdvanceChain(ctx, 2*TestedBlockNumber, nodes[:MirTotalValidatorNumber]...)
require.NoError(t, err)
err = kit.CheckNodesInSync(ctx, 0, nodes[0], nodes[1:MirTotalValidatorNumber]...)
require.NoError(t, err)
}

// TestMirReconfiguration_AddOneValidatorAtHeight tests that the reconfiguration mechanism operates normally
// if a new validator joins the network that have produced 100 blocks.
func TestMirReconfiguration_AddOneValidatorAtHeight(t *testing.T) {
Expand Down