diff --git a/cli/chain.go b/cli/chain.go index b1351549f..e2d0ebb4a 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -1291,7 +1291,7 @@ var chainDecodeParamsCmd = &cli.Command{ return xerrors.Errorf("getting actor: %w", err) } - pstr, err := jsonParams(act.Code, abi.MethodNum(method), params) + pstr, err := JsonParams(act.Code, abi.MethodNum(method), params) if err != nil { return err } diff --git a/cli/state.go b/cli/state.go index d0fcc4f95..13aa5c39b 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1211,7 +1211,7 @@ func ComputeStateHTMLTempl(w io.Writer, ts *types.TipSet, o *api.ComputeStateOut "GetCode": getCode, "GetMethod": getMethod, "ToFil": toFil, - "JsonParams": jsonParams, + "JsonParams": JsonParams, "JsonReturn": jsonReturn, "IsSlow": isSlow, "IsVerySlow": isVerySlow, @@ -1298,7 +1298,7 @@ func sumGas(changes []*types.GasTrace) types.GasTrace { return out } -func jsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, error) { +func JsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, error) { methodMeta, found := stmgr.MethodsMap[code][method] if !found { return "", fmt.Errorf("method %d not found on actor %s", method, code) diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 488e2a6ae..8201ec111 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -45,6 +45,7 @@ func main() { datastoreCmd, ledgerCmd, sectorsCmd, + msgCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go new file mode 100644 index 000000000..63cfc86b9 --- /dev/null +++ b/cmd/lotus-shed/msg.go @@ -0,0 +1,280 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/fatih/color" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" +) + +var msgCmd = &cli.Command{ + Name: "msg", + Usage: "Translate message between various formats", + ArgsUsage: "Message in any form", + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 1 { + return xerrors.Errorf("expected 1 argument") + } + + msg, err := messageFromString(cctx, cctx.Args().First()) + if err != nil { + return err + } + + switch msg := msg.(type) { + case *types.SignedMessage: + return printSignedMessage(cctx, msg) + case *types.Message: + return printMessage(cctx, msg) + default: + return xerrors.Errorf("this error message can't be printed") + } + }, +} + +func printSignedMessage(cctx *cli.Context, smsg *types.SignedMessage) error { + color.Green("Signed:") + color.Blue("CID: %s\n", smsg.Cid()) + + b, err := smsg.Serialize() + if err != nil { + return err + } + color.Magenta("HEX: %x\n", b) + color.Blue("B64: %s\n", base64.StdEncoding.EncodeToString(b)) + jm, err := json.MarshalIndent(smsg, "", " ") + if err != nil { + return xerrors.Errorf("marshaling as json: %w", err) + } + + color.Magenta("JSON: %s\n", string(jm)) + fmt.Println() + fmt.Println("---") + color.Green("Signed Message Details:") + fmt.Printf("Signature(hex): %x\n", smsg.Signature.Data) + fmt.Printf("Signature(b64): %s\n", base64.StdEncoding.EncodeToString(smsg.Signature.Data)) + + sigtype, err := smsg.Signature.Type.Name() + if err != nil { + sigtype = err.Error() + } + fmt.Printf("Signature type: %d (%s)\n", smsg.Signature.Type, sigtype) + + fmt.Println("-------") + return printMessage(cctx, &smsg.Message) +} + +func printMessage(cctx *cli.Context, msg *types.Message) error { + if msg.Version != 0x6d736967 { + color.Green("Unsigned:") + color.Yellow("CID: %s\n", msg.Cid()) + + b, err := msg.Serialize() + if err != nil { + return err + } + color.Cyan("HEX: %x\n", b) + color.Yellow("B64: %s\n", base64.StdEncoding.EncodeToString(b)) + + jm, err := json.MarshalIndent(msg, "", " ") + if err != nil { + return xerrors.Errorf("marshaling as json: %w", err) + } + + color.Cyan("JSON: %s\n", string(jm)) + fmt.Println() + } else { + color.Green("Msig Propose:") + pp := &multisig.ProposeParams{ + To: msg.To, + Value: msg.Value, + Method: msg.Method, + Params: msg.Params, + } + var b bytes.Buffer + if err := pp.MarshalCBOR(&b); err != nil { + return err + } + + color.Cyan("HEX: %x\n", b.Bytes()) + color.Yellow("B64: %s\n", base64.StdEncoding.EncodeToString(b.Bytes())) + jm, err := json.MarshalIndent(pp, "", " ") + if err != nil { + return xerrors.Errorf("marshaling as json: %w", err) + } + + color.Cyan("JSON: %s\n", string(jm)) + fmt.Println() + } + + fmt.Println("---") + color.Green("Message Details:") + fmt.Println("Value:", types.FIL(msg.Value)) + fmt.Println("Max Fees:", types.FIL(msg.RequiredFunds())) + fmt.Println("Max Total Cost:", types.FIL(big.Add(msg.RequiredFunds(), msg.Value))) + + api, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + + defer closer() + ctx := lcli.ReqContext(cctx) + + toact, err := api.StateGetActor(ctx, msg.To, types.EmptyTSK) + if err != nil { + return nil + } + + fmt.Println("Method:", stmgr.MethodsMap[toact.Code][msg.Method].Name) + p, err := lcli.JsonParams(toact.Code, msg.Method, msg.Params) + if err != nil { + return err + } + + fmt.Println("Params:", p) + + return nil +} + +func messageFromString(cctx *cli.Context, smsg string) (types.ChainMsg, error) { + // a CID is least likely to just decode + if c, err := cid.Parse(smsg); err == nil { + return messageFromCID(cctx, c) + } + + // try baseX serializations next + { + // hex first, some hay strings may be decodable as b64 + if b, err := hex.DecodeString(smsg); err == nil { + return messageFromBytes(cctx, b) + } + + // b64 next + if b, err := base64.StdEncoding.DecodeString(smsg); err == nil { + return messageFromBytes(cctx, b) + } + + // b64u?? + if b, err := base64.URLEncoding.DecodeString(smsg); err == nil { + return messageFromBytes(cctx, b) + } + } + + // maybe it's json? + if _, err := messageFromJson(cctx, []byte(smsg)); err == nil { + return nil, err + } + + // declare defeat + return nil, xerrors.Errorf("couldn't decode the message") +} + +func messageFromJson(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { + // Unsigned + { + var msg types.Message + if err := json.Unmarshal(msgb, &msg); err == nil { + if msg.To != address.Undef { + return &msg, nil + } + } + } + + // Signed + { + var msg types.SignedMessage + if err := json.Unmarshal(msgb, &msg); err == nil { + if msg.Message.To != address.Undef { + return &msg, nil + } + } + } + + return nil, xerrors.New("probably not a json-serialized message") +} + +func messageFromBytes(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { + // Signed + { + var msg types.SignedMessage + if err := msg.UnmarshalCBOR(bytes.NewReader(msgb)); err == nil { + return &msg, nil + } + } + + // Unsigned + { + var msg types.Message + if err := msg.UnmarshalCBOR(bytes.NewReader(msgb)); err == nil { + return &msg, nil + } + } + + // Multisig propose? + { + var pp multisig.ProposeParams + if err := pp.UnmarshalCBOR(bytes.NewReader(msgb)); err == nil { + i, err := address.NewIDAddress(0) + if err != nil { + return nil, err + } + + return &types.Message{ + // Hack(-ish) + Version: 0x6d736967, + From: i, + + To: pp.To, + Value: pp.Value, + + Method: pp.Method, + Params: pp.Params, + + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + }, nil + } + } + + // Encoded json??? + { + if msg, err := messageFromJson(cctx, msgb); err == nil { + return msg, nil + } + } + + return nil, xerrors.New("probably not a cbor-serialized message") +} + +func messageFromCID(cctx *cli.Context, c cid.Cid) (types.ChainMsg, error) { + api, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return nil, err + } + + defer closer() + ctx := lcli.ReqContext(cctx) + + msgb, err := api.ChainReadObj(ctx, c) + if err != nil { + return nil, err + } + + return messageFromBytes(cctx, msgb) +}