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 all 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
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/ipcagent/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("ipc_queryValidatorSet", nil, &set)
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
2 changes: 2 additions & 0 deletions chain/ipcagent/ipcagent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package ipcagent maintains IPC Agent client.
package ipcagent
87 changes: 87 additions & 0 deletions chain/ipcagent/rpc/rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package rpc

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)
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 {
// 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()
}
132 changes: 132 additions & 0 deletions chain/ipcagent/rpc/rpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package rpc

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"

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

addr "github.com/filecoin-project/go-address"
)

type TestService struct{}

type TestServiceRequest struct {
A int
B int
}

type TestServiceResponse struct {
O int
}

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

func TestClient(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.O)
}

type Validator struct {
Addr addr.Address `json:"addr"`
NetAddr string `json:"net_addr"`
W int64 `json:"weight"`
}

type Set struct {
ConfigurationNumber uint64 `json:"config_number"`
Validators []Validator `json:"validators"`
}

type confServiceResponse struct {
ValidatorSet Set `json:"validator_set"`
}

func TestClientCompatibleWithIPCAgent(t *testing.T) {
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"))

obj := make(map[string]interface{})

d := json.NewDecoder(r.Body)
err := d.Decode(&obj)
require.NoError(t, err)

require.Equal(t, "2.0", obj["jsonrpc"])
require.NotEqual(t, "", obj["id"])
require.NotEqual(t, "", obj["params"])
require.Equal(t, "ipc_queryValidatorSet", obj["method"])

var result = `
{
"jsonrpc": "2.0",
"result": {
"validator_set": {
"config_number": 22,
"validators": [{
"addr": "f1cp4q4lqsdhob23ysywffg2tvbmar5cshia4rweq",
"net_addr": "/ip4/127.0.0.1/tcp/38443/p2p/12D3KooWM4Z6tymWBUC9LQ7NNJ2RtzoakV1vDSyzehzC17Dpo367",
"weight": 0
},
{
"addr": "f1akaouty2buxxwb46l27pzrhl3te2lw5jem67xuy",
"net_addr": "/ip4/127.0.0.1/tcp/40315/p2p/12D3KooWD9DHVsaPvBN5H16aWZ9KDChyrDSKVCnZegsJguuwd76E",
"weight": 0
}
]
}
},
"id": "5577006791947779410"
}
`
_, err = fmt.Fprint(w, result)
require.NoError(t, err)
}))
defer srv.Close()

req := struct {
subnet string
tipSet string
}{
subnet: "/root/test",
tipSet: "QmPK1s3pNYLi9ERiq3BDxKa3XosgWwFRQUydHUtz4YgpqB",
}

var resp *confServiceResponse
c := NewJSONRPCClient(srv.URL, "")
err := c.SendRequest("ipc_queryValidatorSet", req, &resp)
require.NoError(t, err)

require.Equal(t, uint64(22), resp.ValidatorSet.ConfigurationNumber)
require.Equal(t, 2, len(resp.ValidatorSet.Validators))
}
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
Loading