package abciapp import ( "context" "encoding/json" "github.com/did-method-plc/go-didplc" "github.com/gbl08ma/stacktrace" cbornode "github.com/ipfs/go-ipld-cbor" "tangled.org/gbl08ma.com/didplcbft/plc" ) var TransactionActionCreatePlcOp = registerTransactionAction[CreatePlcOpArguments]("CreatePlcOp", processCreatePlcOpTx) type CreatePlcOpArguments struct { DID string `json:"did" refmt:"did"` Operation *didplc.OpEnum `refmt:"operation"` } func (CreatePlcOpArguments) ForAction() TransactionAction { return TransactionActionCreatePlcOp } func init() { cbornode.RegisterCborType(CreatePlcOpArguments{}) cbornode.RegisterCborType(Transaction[CreatePlcOpArguments]{}) } func processCreatePlcOpTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte) (*processResult, error) { tx, err := UnmarshalTransaction[CreatePlcOpArguments](txBytes) if err != nil { return &processResult{ Code: 4000, Log: err.Error(), }, nil } // sadly didplc is really designed to unmarshal JSON, not CBOR // so JSON ends up being the lingua franca for operations inside our PLC implementation too // we also can't instance didplc.Operations directly from the CBOR unmarshaller (the MakeUnmarshalTransformFunc thing) // because the interface makes us lose data (it is not powerful enough to detect the type of a transaction, for instance) // so our PLC internals end up depending on OpEnum, too // the decision to use CBOR for the entire thing at the blockchain transaction level is: // - to make transactions more compact // - to have more of a canonical format for them (we specifically use the stable CBOR format already used by the PLC for signing) // there is one advantage to this approach: by ensuring we first unmarshal the operations into strongly defined types // (e.g. the OpEnum struct of the didplc package) // we avoid accepting malformed data like what happened in https://github.com/did-method-plc/did-method-plc/issues/71 opBytes, err := json.Marshal(tx.Arguments.Operation) if err != nil { return nil, stacktrace.Propagate(err, "internal error") } if writeTx, ok := deps.writeTx.Get(); ok { err = deps.plc.ExecuteOperation(ctx, writeTx, tx.Arguments.DID, opBytes) } else { err = deps.plc.ValidateOperation(ctx, deps.readTx, tx.Arguments.DID, opBytes) } if err != nil { if code, ok := plc.InvalidOperationErrorCode(err); ok { return &processResult{ Code: code, Log: err.Error(), }, nil } return nil, stacktrace.Propagate(err, "internal error") } return &processResult{ Code: 0, }, nil }