Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions PR_tipset_reservations_ffi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# feat: add FVM reservation Begin/End FFI with error messages

This PR wires the ref‑fvm reservation session API through filecoin‑ffi and extends the Begin/End FFI to return short human‑readable error messages alongside status codes. Lotus uses the status codes for gating and the messages for logging; ref‑fvm remains network‑version agnostic.

## Summary

- Add C ABI exports for `FVM_BeginReservations` / `FVM_EndReservations` with status codes plus optional error strings.
- Provide Go wrappers that map status codes to typed errors and attach the engine‑provided message when present.
- Keep the status‑code contract as the only semantic signal for hosts (messages are logging only).

## Changes

### Cgo surface and Go bindings

- **`cgo/fvm.go`**
- Extend C declarations:
- `int32_t FVM_BeginReservations(InnerFvmMachine_t *executor, const uint8_t *cbor_plan_ptr, size_t cbor_plan_len, const uint8_t **error_msg_ptr, size_t *error_msg_len);`
- `int32_t FVM_EndReservations(InnerFvmMachine_t *executor, const uint8_t **error_msg_ptr, size_t *error_msg_len);`
- `void FVM_DestroyReservationErrorMessage(uint8_t *error_msg_ptr, size_t error_msg_len);`
- Add Go wrappers:
- `func FvmBeginReservations(executor *FvmMachine, plan SliceRefUint8) (int32, string)`
- `func FvmEndReservations(executor *FvmMachine) (int32, string)`
- Behaviour:
- Call the C ABI, copy the returned message (if any) into Go memory, and free the FFI allocation via `FVM_DestroyReservationErrorMessage`.

- **`fvm.go`**
- Define typed reservation errors:
- `ErrReservationsNotImplemented`
- `ErrReservationsInsufficientFunds`
- `ErrReservationsSessionOpen`
- `ErrReservationsSessionClosed`
- `ErrReservationsNonZeroRemainder`
- `ErrReservationsPlanTooLarge`
- `ErrReservationsOverflow`
- `ErrReservationsInvariantViolation`
- Implement:
- `ReservationStatusToError(code int32) error` mapping raw status codes to these sentinels.
- `(*FVM).BeginReservations(plan []byte) error`:
- Calls `cgo.FvmBeginReservations`, maps status to a typed error, and if a non‑empty message is present, wraps the error as `fmt.Errorf("%w: %s", baseErr, msg)`.
- `(*FVM).EndReservations() error` with analogous behaviour.
- These APIs are called by Lotus’ FVM wrapper (`chain/vm/fvm.go`) when orchestrating Begin/End reservations.

### Rust FFI glue

- **`rust/src/fvm/machine.rs`**
- Introduce helper:
- `fn set_reservation_error_message_out(error_msg_ptr_out: *mut *const u8, error_msg_len_out: *mut usize, msg: &str)` to allocate and expose a short message over the FFI.
- Add `fn map_reservation_error_to_status(err: ReservationError, error_msg_ptr_out: *mut *const u8, error_msg_len_out: *mut usize) -> FvmReservationStatus` that:
- Maps `fvm4::executor::ReservationError` variants to `FvmReservationStatus`.
- Sets a short message string for non‑OK statuses, e.g.:
- `ErrInsufficientFundsAtBegin { sender }` → message including sender ID.
- `ErrReservationInvariant(reason)` → message including the invariant description.
- Lock poisoning or missing executor → `ErrReservationInvariant` with a descriptive message.
- Update:
- `FVM_BeginReservations(...) -> FvmReservationStatus` to:
- Decode the plan, look up the current `InnerFvmMachine`, and call `executor.begin_reservations(plan)`.
- On error, call `map_reservation_error_to_status` to populate both status and message.
- `FVM_EndReservations() -> FvmReservationStatus` similarly calls `executor.end_reservations()` and uses the mapping helper on error.
- Add tests that:
- Exercise `map_reservation_error_to_status` for representative errors (`InsufficientFundsAtBegin`, `ReservationInvariant`) and verify that:
- Status values are as expected.
- Returned messages are non‑empty and contain the relevant context.
- `FVM_DestroyReservationErrorMessage` can free the allocation safely.

### Tests

Error propagation is verified via integration tests in consuming repositories (e.g. Lotus).

## Notes

- Hosts must continue to base consensus decisions purely on status codes (mapped to typed errors on the Go side). The new messages are for logging and operator diagnostics only.
- The FFI remains agnostic to network version; activation and gating are handled entirely in Lotus using the tipset network version and feature flags.

51 changes: 51 additions & 0 deletions cgo/fvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,23 @@ package cgo
#cgo pkg-config: ${SRCDIR}/../filcrypto.pc
#include "../filcrypto.h"
#include <stdlib.h>

// Reservation session C ABI. These declarations mirror the FVM_BeginReservations
// and FVM_EndReservations exports defined in the Rust FFI.
// Status codes follow the FvmReservationStatus enum:
// 0 = Ok
// 1 = ErrNotImplemented
// 2 = ErrInsufficientFundsAtBegin
// 3 = ErrSessionOpen
// 4 = ErrSessionClosed
// 5 = ErrNonZeroRemainder
// 6 = ErrPlanTooLarge
// 7 = ErrOverflow
// 8 = ErrReservationInvariant
void FVM_DestroyReservationErrorMessage(uint8_t *error_msg_ptr, size_t error_msg_len);
*/
import "C"
import "unsafe"

func CreateFvmMachine(fvmVersion FvmRegisteredVersion, chainEpoch, chainTimestamp, chainId, baseFeeHi, baseFeeLo, baseCircSupplyHi, baseCircSupplyLo, networkVersion uint64, stateRoot SliceRefUint8, tracing, flushAllBlocks bool, blockstoreId, externsId uint64) (*FvmMachine, error) {
resp := (*resultFvmMachine)(C.create_fvm_machine(
Expand Down Expand Up @@ -94,3 +109,39 @@ func FvmMachineFlush(executor *FvmMachine) ([]byte, error) {
}
return (SliceBoxedUint8)(resp.value).copy(), nil
}

// FvmBeginReservations invokes the FVM_BeginReservations C ABI with a CBOR-encoded plan.
// It returns the raw reservation status code as defined by FvmReservationStatus,
// along with an optional, human-readable error message from the engine.
func FvmBeginReservations(executor *FvmMachine, plan SliceRefUint8) (int32, string) {
var msgPtr *C.uint8_t
var msgLen C.size_t

status := C.FVM_BeginReservations((*C.InnerFvmMachine_t)(executor), plan.ptr, plan.len, &msgPtr, &msgLen)

if msgPtr == nil || msgLen == 0 {
return int32(status), ""
}

msgBytes := C.GoBytes(unsafe.Pointer(msgPtr), C.int(msgLen))
C.FVM_DestroyReservationErrorMessage(msgPtr, msgLen)

return int32(status), string(msgBytes)
}

// FvmEndReservations invokes the FVM_EndReservations C ABI and returns the raw status code.
func FvmEndReservations(executor *FvmMachine) (int32, string) {
var msgPtr *C.uint8_t
var msgLen C.size_t

status := C.FVM_EndReservations((*C.InnerFvmMachine_t)(executor), &msgPtr, &msgLen)

if msgPtr == nil || msgLen == 0 {
return int32(status), ""
}

msgBytes := C.GoBytes(unsafe.Pointer(msgPtr), C.int(msgLen))
C.FVM_DestroyReservationErrorMessage(msgPtr, msgLen)

return int32(status), string(msgBytes)
}
68 changes: 68 additions & 0 deletions fvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,42 @@ type FVM struct {
executor *cgo.FvmMachine
}

var (
ErrReservationsNotImplemented = fmt.Errorf("fvm reservations not implemented")
ErrReservationsInsufficientFunds = fmt.Errorf("fvm reservation insufficient funds at begin")
ErrReservationsSessionOpen = fmt.Errorf("fvm reservation session already open")
ErrReservationsSessionClosed = fmt.Errorf("fvm reservation session closed")
ErrReservationsNonZeroRemainder = fmt.Errorf("fvm reservation non-zero remainder at end")
ErrReservationsPlanTooLarge = fmt.Errorf("fvm reservation plan too large")
ErrReservationsOverflow = fmt.Errorf("fvm reservation arithmetic overflow")
ErrReservationsInvariantViolation = fmt.Errorf("fvm reservation invariant violation")
)

func ReservationStatusToError(code int32) error {
switch code {
case 0:
return nil
case 1:
return ErrReservationsNotImplemented
case 2:
return ErrReservationsInsufficientFunds
case 3:
return ErrReservationsSessionOpen
case 4:
return ErrReservationsSessionClosed
case 5:
return ErrReservationsNonZeroRemainder
case 6:
return ErrReservationsPlanTooLarge
case 7:
return ErrReservationsOverflow
case 8:
return ErrReservationsInvariantViolation
default:
return fmt.Errorf("unknown FVM reservation status code: %d", code)
}
}

const (
applyExplicit = iota
applyImplicit
Expand Down Expand Up @@ -119,6 +155,38 @@ func CreateFVM(opts *FVMOpts) (*FVM, error) {
return fvm, nil
}

// BeginReservations starts a reservation session for the given CBOR-encoded plan.
// It returns a typed error based on the underlying FVM reservation status code,
// optionally wrapped with a short human-readable message from the engine.
func (f *FVM) BeginReservations(plan []byte) error {
defer runtime.KeepAlive(f)
status, msg := cgo.FvmBeginReservations(f.executor, cgo.AsSliceRefUint8(plan))
baseErr := ReservationStatusToError(status)
if baseErr == nil {
return nil
}
if msg == "" {
return baseErr
}
return fmt.Errorf("%w: %s", baseErr, msg)
}

// EndReservations ends the active reservation session.
// It returns a typed error based on the underlying FVM reservation status code,
// optionally wrapped with a short human-readable message from the engine.
func (f *FVM) EndReservations() error {
defer runtime.KeepAlive(f)
status, msg := cgo.FvmEndReservations(f.executor)
baseErr := ReservationStatusToError(status)
if baseErr == nil {
return nil
}
if msg == "" {
return baseErr
}
return fmt.Errorf("%w: %s", baseErr, msg)
}

func (f *FVM) ApplyMessage(msgBytes []byte, chainLen uint) (*ApplyRet, error) {
// NOTE: we need to call KeepAlive here (and below) because go doesn't guarantee that the
// receiver will live to the end of the function. If we don't do this, go _will_ garbage
Expand Down
Loading