introduce message prototypes

This introduces message prototypes to applicable API endpoints, which
allows us to invert control of message sending and give the user a
chance to intervene with an interactive ui.

Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
This commit is contained in:
vyzo
2021-05-07 15:30:05 +02:00
committed by Jakub Sztandera
parent 86e90dc6f1
commit e2d0047a2a
20 changed files with 852 additions and 323 deletions
+42 -18
View File
@@ -22,22 +22,24 @@ import (
//go:generate go run github.com/golang/mock/mockgen -destination=servicesmock_test.go -package=cli -self_package github.com/filecoin-project/lotus/cli . ServicesAPI
type ServicesAPI interface {
FullNodeAPI() api.FullNode
GetBaseFee(ctx context.Context) (abi.TokenAmount, error)
// MessageForSend creates a prototype of a message based on SendParams
MessageForSend(ctx context.Context, params SendParams) (*types.Message, error)
MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error)
// DecodeTypedParamsFromJSON takes in information needed to identify a method and converts JSON
// parameters to bytes of their CBOR encoding
DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error)
RunChecksForPrototype(ctx context.Context, prototype *types.Message) ([][]api.MessageCheckStatus, error)
RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error)
// PublishMessage takes in a message prototype and publishes it
// before publishing the message, it runs checks on the node, message and mpool to verify that
// message is valid and won't be stuck.
// if `force` is true, it skips the checks
PublishMessage(ctx context.Context, prototype *types.Message, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error)
PublishMessage(ctx context.Context, prototype *api.MessagePrototype, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error)
// Close ends the session of services and disconnects from RPC, using Services after Close is called
// most likely will result in an error
@@ -50,6 +52,10 @@ type ServicesImpl struct {
closer jsonrpc.ClientCloser
}
func (s *ServicesImpl) FullNodeAPI() api.FullNode {
return s.api
}
func (s *ServicesImpl) Close() error {
if s.closer == nil {
return xerrors.Errorf("Services already closed")
@@ -102,15 +108,24 @@ type CheckInfo struct {
var ErrCheckFailed = fmt.Errorf("check has failed")
func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *types.Message) ([][]api.MessageCheckStatus, error) {
func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error) {
if !prototype.ValidNonce {
nonce, err := s.api.MpoolGetNonce(ctx, prototype.Message.From)
if err != nil {
return nil, xerrors.Errorf("mpool get nonce: %w", err)
}
prototype.Message.Nonce = nonce
prototype.ValidNonce = true
}
var outChecks [][]api.MessageCheckStatus
checks, err := s.api.MpoolCheckMessages(ctx, []*types.Message{prototype})
checks, err := s.api.MpoolCheckMessages(ctx, []*types.Message{&prototype.Message})
if err != nil {
return nil, xerrors.Errorf("message check: %w", err)
}
outChecks = append(outChecks, checks...)
checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.From)
checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.Message.From)
if err != nil {
return nil, xerrors.Errorf("pending mpool check: %w", err)
}
@@ -123,27 +138,30 @@ func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *typ
// Errors with ErrCheckFailed if any of the checks fail
// First group of checks is related to the message prototype
func (s *ServicesImpl) PublishMessage(ctx context.Context,
prototype *types.Message, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) {
prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) {
gasedMsg, err := s.api.GasEstimateMessageGas(ctx, prototype, nil, types.EmptyTSK)
gasedMsg, err := s.api.GasEstimateMessageGas(ctx, &prototype.Message, nil, types.EmptyTSK)
if err != nil {
return nil, nil, xerrors.Errorf("estimating gas: %w", err)
}
*prototype = *gasedMsg
prototype.Message = *gasedMsg
if !force {
checks, err := s.RunChecksForPrototype(ctx, prototype)
if err != nil {
return nil, nil, xerrors.Errorf("running checks: %w", err)
}
if len(checks) != 0 {
return nil, checks, ErrCheckFailed
for _, chks := range checks {
for _, c := range chks {
if !c.OK {
return nil, checks, ErrCheckFailed
}
}
}
}
//TODO: message prototype needs to have "IsNonceSet"
if prototype.Nonce != 0 {
sm, err := s.api.WalletSignMessage(ctx, prototype.From, prototype)
if prototype.ValidNonce {
sm, err := s.api.WalletSignMessage(ctx, prototype.Message.From, &prototype.Message)
if err != nil {
return nil, nil, err
}
@@ -155,7 +173,7 @@ func (s *ServicesImpl) PublishMessage(ctx context.Context,
return sm, nil, nil
}
sm, err := s.api.MpoolPushMessage(ctx, prototype, nil)
sm, err := s.api.MpoolPushMessage(ctx, &prototype.Message, nil)
if err != nil {
return nil, nil, err
}
@@ -177,7 +195,7 @@ type SendParams struct {
Params []byte
}
func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*types.Message, error) {
func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error) {
if params.From == address.Undef {
defaddr, err := s.api.WalletDefaultAddress(ctx)
if err != nil {
@@ -186,7 +204,7 @@ func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*
params.From = defaddr
}
msg := &types.Message{
msg := types.Message{
From: params.From,
To: params.To,
Value: params.Val,
@@ -210,9 +228,15 @@ func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*
} else {
msg.GasLimit = 0
}
validNonce := false
if params.Nonce != nil {
msg.Nonce = *params.Nonce
validNonce = true
}
return msg, nil
prototype := &api.MessagePrototype{
Message: msg,
ValidNonce: validNonce,
}
return prototype, nil
}