rarimo-core

Adding new operation

To provide TSS signature core uses operations and confirmation entities. Operation entity represents some data to be signed by threshold signature producers. Confirmation entity represents information about signature: indexes list, merkle root based on provided list and signature.

Operation entity contains the following fields:

message Operation {
  string index = 1;
  opType operationType = 2;
  google.protobuf.Any details = 3;
  opStatus status = 4;
  string creator = 5;
  uint64 timestamp = 6;
}

To add new operation developer should lead the following steps:

  1. Add operation data definition in the proto/rarimocore.

    Example: proto/ratimocore/op_fee_token_management.proto

     enum FeeTokenManagementType {
       ADD_FEE_TOKEN = 0;
       REMOVE_FEE_TOKEN = 1;
       UPDATE_FEE_TOKEN = 2;
       WITHDRAW_FEE_TOKEN = 3;
     }
    
     message FeeTokenManagement {
       FeeTokenManagementType opType = 1;
       rarimo.rarimocore.tokenmanager.FeeToken token = 2 [(gogoproto.nullable) = false];
       string chain = 3;
       string receiver = 4;
     }
    

    Also, add new operation type in proto/rarimocore/operation.proto.

    Example: proto/rarimocore/operation.proto

     enum opType {
       TRANSFER = 0;
       CHANGE_PARTIES = 1;
       FEE_TOKEN_MANAGEMENT = 2;
     }
    
  2. In x/rarimocore/crypto/operation define the operation content that should implement merkle.Content interface from merkle "github.com/rarimo/go-merkle".

    Example: x/rarimocore/crypto/operation/op_fee_token_management.go

     package operation
    
     import (
       "bytes"
    
       eth "github.com/ethereum/go-ethereum/crypto"
       merkle "github.com/rarimo/go-merkle"
     )
    
     // FeeTokenManagementContent implements the Content interface provided by go-merkle and represents the content stored in the tree.
     type FeeTokenManagementContent struct {
       // Hash of the deposit tx info
       Origin        OriginData
       TargetNetwork string
       // Receiver address on target network
       Receiver []byte
       // Target bridge contract
       TargetContract []byte
       // Can contain any specific data for target chain to validate.
       Data ContentData
     }
    
     var _ merkle.Content = FeeTokenManagementContent{}
    
     func (f FeeTokenManagementContent) CalculateHash() []byte {
       return eth.Keccak256(f.Data, f.Origin[:], []byte(f.TargetNetwork), f.Receiver, f.TargetContract)
     }
    
     // Equals tests for equality of two Contents
     func (f FeeTokenManagementContent) Equals(other merkle.Content) bool {
       if oc, ok := other.(ChangePartiesContent); ok {
         return bytes.Equal(oc.CalculateHash(), f.CalculateHash())
       }
    
       return false
     }
    
  3. In x/rarimocore/crypto/pkg define the following methods: Get{op name} and Get{op name} content.

    Example: x/rarimocore/crypto/operation/op_fee_token_management.go

     package operation
    
     func GetFeeTokenManagement(operation types.Operation) (*types.FeeTokenManagement, error) {
       if operation.OperationType == types.OpType_FEE_TOKEN_MANAGEMENT {
         op := new(types.FeeTokenManagement)
         return op, proto.Unmarshal(operation.Details.Value, op)
       }
       return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidType, "invalid operation type")
     }
    
     func GetFeeTokenManagementContent(strOrigin string, params tokentypes.NetworkParams, op *types.FeeTokenManagement) (*operation.FeeTokenManagementContent, error) {
       return &operation.FeeTokenManagementContent{
         Origin:         origin.NewStringOriginBuilder().SetString(strOrigin).Build().GetOrigin(),
         TargetNetwork:  params.Name,
         Receiver:       hexutil.MustDecode(op.Receiver),
         TargetContract: hexutil.MustDecode(params.Contract),
         Data:           data.NewFeeTokenDataBuilder().SetOpType(op.OpType).SetAmount(op.Token.Amount).SetAmount(op.Token.Amount).Build().GetContent(),
       }, nil
     }
    
    

    Tips: explore the x/rarimocore/crypto/operation/data and x/rarimocore/crypto/operation/origin packages to use some useful utils from it or add the new if required.

  4. Add Get{op name}Content method and the corresponding case block to the x/rarimocore/crypto/pkg/content/main.go

    Example: x/rarimocore/crypto/pkg/content/main.go

    case types.OpType_FEE_TOKEN_MANAGEMENT:
             content, err := GetFeeManagementContent(client, op)
             if err != nil {
                 return nil, err
             }
    
             if content != nil {
                 contents = append(contents, content)
             }
    
     package content
       
     func GetFeeManagementContent(client *grpc.ClientConn, op *types.Operation) (merkle.Content, error) {
        manage, err := pkg.GetFeeTokenManagement(*op)
        if err != nil {
            return nil, errors.Wrap(err, "error parsing operation details")
        }
       
        networkResp, err := token.NewQueryClient(client).NetworkParams(context.TODO(), &token.QueryNetworkParamsRequest{Name: manage.Chain})
        if err != nil {
            return nil, errors.Wrap(err, "error getting network param entry")
        }
       
        feeparams := networkResp.Params.GetFeeParams()
        if err != nil {
            return nil, errors.New("bridge params not found")
        }
       
        content, err := pkg.GetFeeTokenManagementContent(feeparams, manage)
        return content, errors.Wrap(err, "error creating content")
     }
    
  5. In the x/rarimocore/keeper define function that creates the operation and define function call where it is required.

  6. In the x/rarimocore/keeper/msg_server_confirmation.go extend the existing logic of getContent(ctx sdk.Context, op types.Operation) (merkle.Content, error) method. For example add:

     case types.OpType_FEE_TOKEN_MANAGEMENT:
         manage, err := pkg.GetFeeTokenManagement(op)
         if err != nil {
             return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "failed to unmarshal details")
         }
         return k.getFeeTokenManagementContent(ctx, op.Index, manage)
    

    Example:

    func (k msgServer) getContent(ctx sdk.Context, op types.Operation) (merkle.Content, error) {
      switch op.OperationType {
      case types.OpType_TRANSFER:
        transfer, err := pkg.GetTransfer(op)
        if err != nil {
          return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "failed to unmarshal details")
        }
    
        return k.getTransferOperationContent(ctx, transfer)
      case types.OpType_CHANGE_PARTIES:
        change, err := pkg.GetChangeParties(op)
        if err != nil {
          return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "failed to unmarshal details")
        }
    
        return pkg.GetChangePartiesContent(change)
      case types.OpType_FEE_TOKEN_MANAGEMENT:
        manage, err := pkg.GetFeeTokenManagement(op)
        if err != nil {
          return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "failed to unmarshal details")
        }
        return k.getFeeTokenManagementContent(ctx, op.Index, manage)
      default:
        return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid operation")
      }
    }
    
  7. Also, you can provide additional logic in ApplyOperation(ctx sdk.Context, op types.Operation) error to execute some stuff after signing if required.

  8. Update the core dependency version in tss-svc. (It will use methods that you’ve defined in 4)