Skip to content

Commit

Permalink
feat: add tooling for tests (packages libcmt, gollup, and snapshot)
Browse files Browse the repository at this point in the history
  • Loading branch information
renan061 committed Jul 1, 2024
1 parent 46438d4 commit deeff57
Show file tree
Hide file tree
Showing 6 changed files with 634 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ linters-settings:
mnd:
ignored-functions:
- "^make"
issues:
exclude-files:
- test/libcmt/libcmt.go
- test/gollup/gollup.go
5 changes: 2 additions & 3 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ EOF
# STAGE: emulator-devel
#
# - Install libarchive13 (setup -- required by xgenext2fs).
# - Install gcc-12-riscv64-linux-gnu (setup -- required to compile libcmt applications).
# - Install libcmt.
# - Install xgenext2fs.
# =============================================================================
Expand All @@ -122,6 +123,7 @@ RUN <<EOF
set -e
apt-get update
apt-get install -y --no-install-recommends libarchive13
apt-get install -y gcc-12-riscv64-linux-gnu
EOF

# Install libcmt (cartesi/machine-emulator-tools).
Expand All @@ -133,9 +135,6 @@ RUN <<EOF
curl -fsSL ${URL}/${VERSION}/${ARTIFACT} -o ./libcmt.deb
apt-get install -y ./libcmt.deb
rm ./libcmt.deb
mv /usr/riscv64-linux-gnu/include/libcmt /usr/include/
mv /usr/riscv64-linux-gnu/lib/libcmt.a /usr/lib/
rm -rf /usr/riscv64-linux-gnu
EOF

# Install xgenext2fs (cartesi/genext2fs).
Expand Down
102 changes: 102 additions & 0 deletions test/gollup/gollup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

// Package gollup wraps the functionalities provided by libcmt in a rollups-oriented interface.
package gollup

import "github.com/cartesi/rollups-node/test/libcmt"

type (
Address = libcmt.Address
Input = libcmt.Input
Query = libcmt.Query
)

type OutputEmitter interface {
SendVoucher(address Address, value []byte, data []byte) (uint64, error)
SendNotice(data []byte) (uint64, error)
SendReport(data []byte) error
RaiseException(data []byte) error
}

type ReportEmitter interface {
SendReport(data []byte) error
RaiseException(data []byte) error
}

// ------------------------------------------------------------------------------------------------

type AdvanceHandler func(OutputEmitter, Input) (accept bool)

type InspectHandler func(ReportEmitter, Query) (accept bool)

type Gollup struct {
rollup *libcmt.Rollup
advanceHandler AdvanceHandler
inspectHandler InspectHandler
}

// New returns a new [Gollup].
func New(advanceHandler AdvanceHandler, inspectHandler InspectHandler) (*Gollup, error) {
rollup, err := libcmt.NewRollup()
if err != nil {
return nil, err
}
return &Gollup{rollup, advanceHandler, inspectHandler}, nil
}

// Close closes the rollup, rendering it unusable.
// Close will return an error if it has already been called.
func (gollup *Gollup) Close() {
gollup.rollup.Close()
}

// Run runs a loop that perpetually checks for and handles new requests.
func (gollup *Gollup) Run() error {
accept := true
for {
requestType, err := gollup.rollup.Finish(accept)
if err != nil {
return err
}

switch requestType {
case libcmt.AdvanceState:
input, err := gollup.rollup.ReadAdvanceState()
if err != nil {
return err
}
accept = gollup.advanceHandler(gollup, input)
case libcmt.InspectState:
query, err := gollup.rollup.ReadInspectState()
if err != nil {
return err
}
accept = gollup.inspectHandler(gollup, query)
default:
panic("unreachable")
}
}
}

// ------------------------------------------------------------------------------------------------

func (gollup *Gollup) SendVoucher(
address libcmt.Address,
value []byte,
data []byte,
) (uint64, error) {
return gollup.rollup.EmitVoucher(address, value, data)
}

func (gollup *Gollup) SendNotice(data []byte) (uint64, error) {
return gollup.rollup.EmitNotice(data)
}

func (gollup *Gollup) SendReport(data []byte) error {
return gollup.rollup.EmitReport(data)
}

func (gollup *Gollup) RaiseException(data []byte) error {
return gollup.rollup.EmitException(data)
}
183 changes: 183 additions & 0 deletions test/libcmt/libcmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

// Package libcmt provides bindings for the libcmt C library.
// It facilitates the development of applications meant to run in the cartesi-machine
// by handling IO and the communication protocol with the machine emulator.
package libcmt

// #cgo CFLAGS: -I/usr/riscv64-linux-gnu/include
// #cgo LDFLAGS: -L/usr/riscv64-linux-gnu/lib -lcmt
// #include <string.h>
// #include "libcmt/abi.h"
// #include "libcmt/io.h"
// #include "libcmt/rollup.h"
import "C"
import (
"errors"
"fmt"
)

type (
RequestType uint8
Address = [20]byte
)

var (
ErrClosed = errors.New("rollup already closed")

ErrFinish = errors.New("failed to finish")
ErrReadAdvanceState = errors.New("failed to read the advance's state")
ErrReadInspectState = errors.New("failed to read the inspect's state")
ErrEmitVoucher = errors.New("failed to emit voucher")
ErrEmitNotice = errors.New("failed to emit notice")
ErrEmitReport = errors.New("failed to emit report")
ErrEmitException = errors.New("failed to emit exception")
)

const (
AdvanceState RequestType = C.HTIF_YIELD_REASON_ADVANCE
InspectState RequestType = C.HTIF_YIELD_REASON_INSPECT
)

type Input struct {
ChainId uint64
AppContract Address
Sender Address
BlockNumber uint64
BlockTimestamp uint64
Index uint64
Data []byte
}

type Query struct {
Data []byte
}

type Rollup struct{ c *C.cmt_rollup_t }

// NewRollup returns a new [Rollup].
func NewRollup() (*Rollup, error) {
var c C.cmt_rollup_t
errno := C.cmt_rollup_init(&c)
rollup := &Rollup{c: &c}
return rollup, toError(errno)
}

// Close closes the rollup, rendering it unusable.
// Close will return an error if it has already been called.
func (rollup *Rollup) Close() error {
if rollup.c == nil {
return ErrClosed
}
C.cmt_rollup_fini(rollup.c)
rollup.c = nil
return nil
}

// Finish accepts or rejects the previous advance/inspect request.
// It then waits for the next request and returns its type.
func (rollup *Rollup) Finish(accept bool) (RequestType, error) {
finish := C.cmt_rollup_finish_t{accept_previous_request: C.bool(accept)}
errno := C.cmt_rollup_finish(rollup.c, &finish)
if err := toError(errno); err != nil {
return 0, fmt.Errorf("%w: %w", ErrFinish, err)
}
return RequestType(finish.next_request_type), nil
}

// ReadAdvanceState returns the [Input] from an advance-state request.
func (rollup *Rollup) ReadAdvanceState() (Input, error) {
var advance C.cmt_rollup_advance_t
errno := C.cmt_rollup_read_advance_state(rollup.c, &advance)
if err := toError(errno); err != nil {
return Input{}, fmt.Errorf("%w: %w", ErrReadAdvanceState, err)
}
return Input{
ChainId: uint64(advance.chain_id),
AppContract: toAddress(advance.app_contract),
Sender: toAddress(advance.msg_sender),
BlockNumber: uint64(advance.block_number),
BlockTimestamp: uint64(advance.block_timestamp),
Index: uint64(advance.index),
Data: C.GoBytes(advance.payload, C.int(advance.payload_length)),
}, nil
}

// ReadInspectState returns the [Query] from an inspect-state request.
func (rollup *Rollup) ReadInspectState() (Query, error) {
var inspect C.cmt_rollup_inspect_t
errno := C.cmt_rollup_read_inspect_state(rollup.c, &inspect)
if err := toError(errno); err != nil {
return Query{}, fmt.Errorf("%w: %w", ErrReadInspectState, err)
}
return Query{Data: C.GoBytes(inspect.payload, C.int(inspect.payload_length))}, nil
}

// EmitVoucher emits a voucher and returns its index.
func (rollup *Rollup) EmitVoucher(address Address, value []byte, voucher []byte) (uint64, error) {
addressData := C.CBytes(address[:])
// defer C.free(addressData)

valueLength, valueData := C.uint(len(value)), C.CBytes(value)
// defer C.free(valueData)

voucherLength, voucherData := C.uint(len(voucher)), C.CBytes(voucher)
// defer C.free(voucherData)

var index C.uint64_t
err := toError(C.cmt_rollup_emit_voucher(rollup.c,
C.CMT_ADDRESS_LENGTH, addressData,
valueLength, valueData,
voucherLength, voucherData,
&index,
))

return uint64(index), fmt.Errorf("%w: %w", ErrEmitVoucher, err)
}

// EmitNotice emits a notice and returns its index.
func (rollup *Rollup) EmitNotice(notice []byte) (uint64, error) {
length, data := C.uint(len(notice)), C.CBytes(notice)
// defer C.free(data)
var index C.uint64_t
err := toError(C.cmt_rollup_emit_notice(rollup.c, length, data, &index))
return uint64(index), fmt.Errorf("%w: %w", ErrEmitNotice, err)
}

// EmitReport emits a report.
func (rollup *Rollup) EmitReport(report []byte) error {
length, data := C.uint(len(report)), C.CBytes(report)
// defer C.free(data)
err := toError(C.cmt_rollup_emit_report(rollup.c, length, data))
return fmt.Errorf("%w: %w", ErrEmitReport, err)

}

// EmitException emits an exception.
func (rollup *Rollup) EmitException(exception []byte) error {
length, data := C.uint(len(exception)), C.CBytes(exception)
// defer C.free(data)
err := toError(C.cmt_rollup_emit_exception(rollup.c, length, data))
return fmt.Errorf("%w: %w", ErrEmitException, err)
}

// ------------------------------------------------------------------------------------------------

func toError(errno C.int) error {
if errno < 0 {
s := C.strerror(-errno)
// defer C.free(unsafe.Pointer(s))
return fmt.Errorf("%s (%d)", C.GoString(s), errno)
} else {
return nil
}
}

func toAddress(c [C.CMT_ADDRESS_LENGTH]C.uint8_t) Address {
var address Address
for i, v := range c {
address[i] = byte(v)
}
return address
}
Loading

0 comments on commit deeff57

Please sign in to comment.