Skip to content

Commit

Permalink
fix(f3): handle jwt properly in f3 sidecar and set proper permissions…
Browse files Browse the repository at this point in the history
… for F3.* RPC methods (#4902)
  • Loading branch information
hanabi1224 authored Oct 16, 2024
1 parent 7b3b90f commit f4330f7
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 39 deletions.
5 changes: 3 additions & 2 deletions f3-sidecar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ Follow https://go.dev/doc/install or use one of the version managers of Go.

### Run sidecar

- run a forest node on calibnet
- run a forest node on calibnet (make sure to save jwt token with
`--save-token jwt_path`)
- import a shared miner key for testing `forest-wallet --remote-wallet import`
(the shared miner worker key can be found in `scripts/tests/api_compare/.env`)
- run f3 sidecar `go run .`
- run f3 sidecar `go run . -jwt $(cat jwt_path)`
- (optional) to inspect RPC calls, run
`mitmproxy --mode reverse:http://localhost:2345 --listen-port 8080` then
`go run . -rpc http://127.0.0.1:8080/rpc/v1`
Expand Down
26 changes: 20 additions & 6 deletions f3-sidecar/ec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@ package main

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

"github.com/filecoin-project/go-f3/ec"
"github.com/filecoin-project/go-f3/gpbft"
"github.com/filecoin-project/go-jsonrpc"
)

type ForestEC struct {
rpcEndpoint string
f3api F3Api
closer jsonrpc.ClientCloser
rpcEndpoint string
isJwtProvided bool
f3api F3Api
closer jsonrpc.ClientCloser
}

func NewForestEC(rpcEndpoint string) (ForestEC, error) {
func NewForestEC(rpcEndpoint, jwt string) (ForestEC, error) {
f3api := F3Api{}
closer, err := jsonrpc.NewClient(context.Background(), rpcEndpoint, "F3", &f3api, nil)
headers := make(http.Header)
isJwtProvided := len(jwt) > 0
if isJwtProvided {
headers.Add("Authorization", fmt.Sprintf("Bearer %s", jwt))
}
closer, err := jsonrpc.NewClient(context.Background(), rpcEndpoint, "F3", &f3api, headers)
if err != nil {
return ForestEC{}, err
}
return ForestEC{rpcEndpoint, f3api, closer}, nil
return ForestEC{rpcEndpoint, isJwtProvided, f3api, closer}, nil
}

func (ec *ForestEC) Close() {
Expand Down Expand Up @@ -48,10 +56,16 @@ func (ec *ForestEC) GetPowerTable(ctx context.Context, tsk gpbft.TipSetKey) (gpb
}

func (ec *ForestEC) Finalize(ctx context.Context, tsk gpbft.TipSetKey) error {
if !ec.isJwtProvided {
return fmt.Errorf("unable to finalize tipset, jwt is not provided")
}
return ec.f3api.Finalize(ctx, tsk)
}

func (ec *ForestEC) Sign(ctx context.Context, sender gpbft.PubKey, msg []byte) ([]byte, error) {
if !ec.isJwtProvided {
return nil, fmt.Errorf("unable to sign messages, jwt is not provided")
}
signature, err := ec.f3api.SignMessage(ctx, sender, msg)
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions f3-sidecar/ec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
)

func init() {
ec, err := NewForestEC("http://127.0.0.1:2345/rpc/v1")
ec, err := NewForestEC("http://127.0.0.1:2345/rpc/v1", "")
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -69,5 +69,5 @@ func TestFinalize(t *testing.T) {
head, err := EC.GetHead(ctx)
require.NoError(t, err)
err = EC.Finalize(ctx, head.Key())
require.NoError(t, err)
require.ErrorContains(t, err, "unable to finalize tipset, jwt is not provided")
}
6 changes: 3 additions & 3 deletions f3-sidecar/ffi_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ import (
var GoF3NodeImpl GoF3Node

type GoF3Node interface {
run(rpc_endpoint string, f3_rpc_endpoint string, initial_power_table string, bootstrap_epoch int64, finality int64, f3_root string, manifest_server string) bool
run(rpc_endpoint string, jwt string, f3_rpc_endpoint string, initial_power_table string, bootstrap_epoch int64, finality int64, f3_root string, manifest_server string) bool
}

//export CGoF3Node_run
func CGoF3Node_run(rpc_endpoint C.StringRef, f3_rpc_endpoint C.StringRef, initial_power_table C.StringRef, bootstrap_epoch C.int64_t, finality C.int64_t, f3_root C.StringRef, manifest_server C.StringRef, slot *C.void, cb *C.void) {
resp := GoF3NodeImpl.run(newString(rpc_endpoint), newString(f3_rpc_endpoint), newString(initial_power_table), newC_int64_t(bootstrap_epoch), newC_int64_t(finality), newString(f3_root), newString(manifest_server))
func CGoF3Node_run(rpc_endpoint C.StringRef, jwt C.StringRef, f3_rpc_endpoint C.StringRef, initial_power_table C.StringRef, bootstrap_epoch C.int64_t, finality C.int64_t, f3_root C.StringRef, manifest_server C.StringRef, slot *C.void, cb *C.void) {
resp := GoF3NodeImpl.run(newString(rpc_endpoint), newString(jwt), newString(f3_rpc_endpoint), newString(initial_power_table), newC_int64_t(bootstrap_epoch), newC_int64_t(finality), newString(f3_root), newString(manifest_server))
resp_ref, buffer := cvt_ref(cntC_bool, refC_bool)(&resp)
C.GoF3Node_run_cb(unsafe.Pointer(cb), resp_ref, unsafe.Pointer(slot))
runtime.KeepAlive(resp)
Expand Down
4 changes: 2 additions & 2 deletions f3-sidecar/ffi_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ type f3Impl struct {
ctx context.Context
}

func (f3 *f3Impl) run(rpc_endpoint string, f3_rpc_endpoint string, initial_power_table string, bootstrap_epoch int64, finality int64, db string, manifest_server string) bool {
err := run(f3.ctx, rpc_endpoint, f3_rpc_endpoint, initial_power_table, bootstrap_epoch, finality, db, manifest_server)
func (f3 *f3Impl) run(rpc_endpoint string, jwt string, f3_rpc_endpoint string, initial_power_table string, bootstrap_epoch int64, finality int64, db string, manifest_server string) bool {
err := run(f3.ctx, rpc_endpoint, jwt, f3_rpc_endpoint, initial_power_table, bootstrap_epoch, finality, db, manifest_server)
return err == nil
}

Expand Down
4 changes: 3 additions & 1 deletion f3-sidecar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func main() {

var rpcEndpoint string
flag.StringVar(&rpcEndpoint, "rpc", "http://127.0.0.1:2345/rpc/v1", "forest RPC endpoint")
var jwt string
flag.StringVar(&jwt, "jwt", "", "the JWT token for invoking forest RPC methods that require WRITE and SIGN permission")
var f3RpcEndpoint string
flag.StringVar(&f3RpcEndpoint, "f3-rpc", "127.0.0.1:23456", "The RPC endpoint F3 sidecar listens on")
var initialPowerTable string
Expand All @@ -42,7 +44,7 @@ func main() {

ctx := context.Background()

err := run(ctx, rpcEndpoint, f3RpcEndpoint, initialPowerTable, bootstrapEpoch, finality, root, manifestServer)
err := run(ctx, rpcEndpoint, jwt, f3RpcEndpoint, initialPowerTable, bootstrapEpoch, finality, root, manifestServer)
if err != nil {
panic(err)
}
Expand Down
40 changes: 23 additions & 17 deletions f3-sidecar/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import (
"github.com/libp2p/go-libp2p/core/peer"
)

func run(ctx context.Context, rpcEndpoint string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string, manifestServer string) error {
func run(ctx context.Context, rpcEndpoint string, jwt string, f3RpcEndpoint string, initialPowerTable string, bootstrapEpoch int64, finality int64, f3Root string, manifestServer string) error {
api := FilecoinApi{}
isJwtProvided := len(jwt) > 0
closer, err := jsonrpc.NewClient(context.Background(), rpcEndpoint, "Filecoin", &api, nil)
if err != nil {
return err
Expand All @@ -41,7 +42,7 @@ func run(ctx context.Context, rpcEndpoint string, f3RpcEndpoint string, initialP
if err != nil {
return err
}
ec, err := NewForestEC(rpcEndpoint)
ec, err := NewForestEC(rpcEndpoint, jwt)
if err != nil {
return err
}
Expand Down Expand Up @@ -155,23 +156,28 @@ func run(ctx context.Context, rpcEndpoint string, f3RpcEndpoint string, initialP
if err != nil {
continue
}
for _, miner := range miners {
signatureBuilder, err := msgToSign.PrepareSigningInputs(gpbft.ActorID(miner))
if err != nil {
if errors.Is(err, gpbft.ErrNoPower) {
// we don't have any power in F3, continue
logger.Warnf("no power to participate in F3: %+v", err)
} else {
logger.Warnf("preparing signing inputs: %+v", err)
if !isJwtProvided && len(miners) > 0 {
logger.Warn("Unable to sign messages, jwt for Forest RPC endpoint is not provided.")
}
if isJwtProvided {
for _, miner := range miners {
signatureBuilder, err := msgToSign.PrepareSigningInputs(gpbft.ActorID(miner))
if err != nil {
if errors.Is(err, gpbft.ErrNoPower) {
// we don't have any power in F3, continue
logger.Warnf("no power to participate in F3: %+v", err)
} else {
logger.Warnf("preparing signing inputs: %+v", err)
}
continue
}
continue
}
payloadSig, vrfSig, err := signatureBuilder.Sign(ctx, &ec)
if err != nil {
logger.Warnf("signing message: %+v", err)
payloadSig, vrfSig, err := signatureBuilder.Sign(ctx, &ec)
if err != nil {
logger.Warnf("signing message: %+v", err)
}
logger.Debugf("miner with id %d is sending message in F3", miner)
f3Module.Broadcast(ctx, signatureBuilder, payloadSig, vrfSig)
}
logger.Debugf("miner with id %d is sending message in F3", miner)
f3Module.Broadcast(ctx, signatureBuilder, payloadSig, vrfSig)
}
}
}
9 changes: 5 additions & 4 deletions src/daemon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pub(super) async fn start(
keystore.put(JWT_IDENTIFIER, generate_priv_key())?;
}

handle_admin_token(&opts, &keystore)?;
let admin_jwt = handle_admin_token(&opts, &keystore)?;

let keystore = Arc::new(RwLock::new(keystore));

Expand Down Expand Up @@ -427,6 +427,7 @@ pub(super) async fn start(
crate::f3::run_f3_sidecar_if_enabled(
&chain_config,
format!("http://{rpc_address}/rpc/v1"),
admin_jwt,
crate::rpc::f3::get_f3_rpc_endpoint().to_string(),
initial_power_table.to_string(),
bootstrap_epoch,
Expand Down Expand Up @@ -595,7 +596,7 @@ async fn set_snapshot_path_if_needed(

/// Generates, prints and optionally writes to a file the administrator JWT
/// token.
fn handle_admin_token(opts: &CliOpts, keystore: &KeyStore) -> anyhow::Result<()> {
fn handle_admin_token(opts: &CliOpts, keystore: &KeyStore) -> anyhow::Result<String> {
let ki = keystore.get(JWT_IDENTIFIER)?;
// Lotus admin tokens do not expire but Forest requires all JWT tokens to
// have an expiration date. So we set the expiration date to 100 years in
Expand All @@ -608,10 +609,10 @@ fn handle_admin_token(opts: &CliOpts, keystore: &KeyStore) -> anyhow::Result<()>
)?;
info!("Admin token: {token}");
if let Some(path) = opts.save_token.as_ref() {
std::fs::write(path, token)?;
std::fs::write(path, &token)?;
}

Ok(())
Ok(token)
}

/// returns the first error with which any of the services end, or never returns at all
Expand Down
1 change: 1 addition & 0 deletions src/f3/go_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod binding {
pub trait GoF3Node {
fn run(
rpc_endpoint: String,
jwt: String,
f3_rpc_endpoint: String,
initial_power_table: String,
bootstrap_epoch: i64,
Expand Down
2 changes: 2 additions & 0 deletions src/f3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub fn get_f3_sidecar_params(chain_config: &ChainConfig) -> F3Options {
pub fn run_f3_sidecar_if_enabled(
chain_config: &ChainConfig,
_rpc_endpoint: String,
_jwt: String,
_f3_rpc_endpoint: String,
_initial_power_table: String,
_bootstrap_epoch: i64,
Expand All @@ -101,6 +102,7 @@ pub fn run_f3_sidecar_if_enabled(
{
GoF3NodeImpl::run(
_rpc_endpoint,
_jwt,
_f3_rpc_endpoint,
_initial_power_table,
_bootstrap_epoch,
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/methods/f3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ impl RpcMethod<1> for Finalize {
const NAME: &'static str = "F3.Finalize";
const PARAM_NAMES: [&'static str; 1] = ["tipset_key"];
const API_PATHS: ApiPaths = ApiPaths::V1;
const PERMISSION: Permission = Permission::Read;
const PERMISSION: Permission = Permission::Write;

type Params = (F3TipSetKey,);
type Ok = ();
Expand Down Expand Up @@ -524,7 +524,7 @@ impl RpcMethod<2> for SignMessage {
const NAME: &'static str = "F3.SignMessage";
const PARAM_NAMES: [&'static str; 2] = ["pubkey", "message"];
const API_PATHS: ApiPaths = ApiPaths::V1;
const PERMISSION: Permission = Permission::Read;
const PERMISSION: Permission = Permission::Sign;

type Params = (Vec<u8>, Vec<u8>);
type Ok = Signature;
Expand Down

0 comments on commit f4330f7

Please sign in to comment.