UPDATE Feb 17, 2023: David Thompson presented a walk-trough about how he implemented the Networked Garden game at DecentSocial 2023
Hello! This is David Thompson, Spritely Institute’s new Core Infrastructure Architect! I’m writing this post on a Friday, at the end of my first week here, to share my experience with Spritely Goblins so far. My goal for this week was to learn as much as I could about actors, object capabilities, and the Goblins API in order to create a small demo program to demonstrate my progress. I had my work cut out for me because, although I'm very comfortable with Scheme and functional programming, the domain of object capability security is one in which I have no prior experience (aside from studying and providing feedback on The Heart of Spritely before I joined!)
The good news is that Spritely Goblins has been thoughtfully designed to allow programmers to start small and iteratively build an application that can be used over a network. This post will walk through the steps I took to get from a concept to a demo that my fellow Spritely coworkers could interact with over the Tor network. You can find the full source code for this demo on GitLab.
Phase 0: The concept
When I’m not at the computer, I like to garden, so I thought it would be fun to incorporate gardening into my demo. Christine suggested I make a tile-based garden game where multiple people could plant things. I thought that was a great idea, so I ran with it. This very simple game is modeled after a community garden. Community gardens are typically small plots of land that a group of local people cultivate cooperatively. Those that join a community garden are expected to follow a set of rules meant to keep the garden a happy and productive space.
My demo was made in just a couple of days, so it would be impossible to simulate all the details of what it’s like to participate in a community garden. For example: Every gardener gets a chunk of the available growing space in a real community garden, and such a system is certainly interesting from an object capability security perspective, but I felt that it would take too long to implement. I chose to focus on three things: Planting, digging up plants, and specifying which plants are acceptable for the community garden. The game takes place on a simple 2-dimensional grid of 8 by 8 tiles, where any gardener can plant in any space provided that the plant has been approved for use in the garden.
Phase 1: Local interaction with text-based visualization
As a first step, I wanted to figure out the minimal number of actors I needed to model the simple interactions explained in the previous section. I came up with 4:
- The Garden: The shared growing space.
- The Gardener: Someone who can modify a garden.
- The Botanist: The resident plant expert that says which plants are allowed to be grown in the garden.
- The Garden Gate: The place where gardeners check-in with the botanist before they sow/transplant to make sure it's OK for the plant to be grown in the garden.
Implementing the botanist was an opportunity to practice using sealers:
(define (^botanist bcom) (define-values (seal-plant unseal-plant approved-plant?) (make-sealer-triplet)) (methods ((approve-plant plant) (seal-plant plant)) ((check-plant plant) (if (approved-plant? plant) (unseal-plant plant) (error "plant is not allowed" plant)))))
Whoever has access to the botanist can approve plants by sending the
approve-plant message. In return they will receive a sealed plant
The garden gate actor is simply a proxy for the botanist:
(define (^garden-gate bcom botanist) (methods ((check-plant plant) ($ botanist 'check-plant plant))))
Whoever has access to the garden gate can check if plants are approved, but they are not allowed to approve plants.
The garden actor manages the state of the community garden plot:
(define (^garden bcom name garden-bed garden-gate) (define (ensure-empty x y) (when (garden-bed-ref garden-bed x y) (error "tile already has something planted in it" x y))) (methods ((get-name) name) ((get-bed) garden-bed) ((plant x y sealed-plant) (ensure-empty x y) (let* ((plant ($ garden-gate 'check-plant sealed-plant)) (new-bed (garden-bed-set garden-bed x y plant))) (bcom (^garden bcom name new-bed garden-gate)))) ((dig-up x y) (let ((new-bed (garden-bed-set garden-bed x y #f))) (bcom (^garden bcom name new-bed garden-gate))))))
Whoever has access to the garden can plant or dig up whatever tile they'd like, but they have to check in at the garden gate before planting.
Finally, there's the gardener:
(define (^gardener bcom name garden) (methods ((get-name) name) ((get-garden-name) (<- garden 'get-name)) ((inspect-garden) (<- garden 'get-bed)) ((plant x y plant) (<- garden 'plant x y plant)) ((dig-up x y) (<- garden 'dig-up x y))))
Gardeners belong to a garden, so their actions (planting, digging) are done within the context of their garden.
With these actors in place, I could now create a garden and have a gardener make changes to it.
;; Build the garden (define garden-vat (spawn-vat)) (define-vat-run garden-run garden-vat) (define the-botanist (garden-run (spawn ^botanist))) (define the-garden-gate (garden-run (spawn ^garden-gate the-botanist))) (define our-garden (garden-run (spawn ^garden "Spritely Institute Community Garden" (make-garden-bed 8 8) the-garden-gate))) ;; Sunflowers are allowed. (define sunflower/approved (garden-run ($ the-botanist 'approve-plant sunflower))) ;; Alice likes to garden. (define alice-vat (spawn-vat)) (define-vat-run alice-run alice-vat) (define alice (alice-run (spawn ^gardener "Alice" our-garden))) (alice-run ($ alice 'plant 2 2 sunflower/approved)) ;; ... more planting
display-garden-bed procedure was made to visualize the
state of the garden:
Phase 2: Local interaction with graphical visualization
Text is nice, but I wanted something a little more eye catching to
share. So, I integrated the Goblins code with the
programming library to create a simple 2D visualization using sprites.
The result is the screenshot at the beginning of the post. The
visualization program periodically polls a special read-only actor
(called a “visitor”) for the current state of the garden by sending
inspect-garden message and updates the sprites to reflect the
current state of the garden.
Phase 3: Network interaction over Tor via OCapN
This is where things get really interesting. Using the Object Capabilities Network (OCapN) support in Goblins, I generated a special URI that represents the garden. By giving someone this URI (called a “sturdy ref”), they gain the ability to edit the garden.
OCapN can operate over different types of networks (called “netlayers.”) Tor is used here:
(define onion-netlayer (garden-run (new-onion-netlayer))) (define mycapn (garden-run (let* ((mycapn (spawn-mycapn onion-netlayer)) (garden-sref ($ mycapn 'register our-garden 'onion))) (format #t "Connect to: ~a\n" (ocapn-id->string garden-sref)) mycapn)))
This is the only code that needed to be added to make the garden reachable over the network by those who have been given the sturdy ref.
To make it possible for remote users to plant things, the botanist actor was enhanced to keep a registry of approved plants: A mapping from string (the plant’s name) to sealed plant object. In order to pass this list of sealed plants over the network, I switched from “simple sealers” to the freshly merged “actor sealers” in the implementation of the botanist.
A second program was created that allows the user to edit the remote garden. It receives the garden sturdy ref via a command-line argument. I didn’t have time to make a graphical program for remote editing, so instead I made a very barebones text command console. Then, I asked Christine to edit the garden from their computer. This program isn’t smart enough to tell Christine that planting radishes isn’t allowed, but the botanist only likes cabbages and sunflowers!
Here's the result as seen from the graphical viewer program:
Let’s get a glimpse of the edit program:
(define (edit-garden name garden-address) (define garden-sref (string->ocapn-id garden-address)) (define vat (spawn-vat)) (define-vat-run vat-run vat) (define onion-netlayer (vat-run (new-onion-netlayer))) (define mycapn (vat-run (spawn-mycapn onion-netlayer))) (define garden-vow (vat-run ($ mycapn 'enliven garden-sref))) (define approved-plants-vow (vat-run (<- garden-vow 'get-approved-plants))) (define gardener (vat-run (spawn ^gardener name garden-vow))) ...)
The reference to the remote garden is “enlivened” via OCapN to create
an actor, which can then be sent messages. The
gardener actor works
just like it did when it was talking to a local garden. Neat!
Phase 4: Audit logging
As a finishing touch, I wanted to display a text log of what was happening to the garden, inspired by the “Revocation and accountability” section of the Heart of Spritely paper. This required a small change to the architecture. A new actor called the “Garden Community” was introduced to wrap the garden.
(define (^garden-community bcom garden) (define garden-name ($ garden 'get-name)) (define (^gardener bcom name) (methods ((get-name) name) ((get-garden-name) garden-name) ((get-approved-plants) ($ garden 'get-approved-plants)) ((inspect-garden) ($ garden 'get-bed)) ((plant x y plant) ($ garden 'plant name x y plant)) ((dig-up x y) ($ garden 'dig-up name x y)))) (define (^visitor bcom name) (methods ((get-name) name) ((get-garden-name) garden-name) ((inspect-garden) ($ garden 'get-bed)))) (methods ((register-gardener name) (format #t "~a has joined ~a\n" name garden-name) (spawn ^gardener name)) ((register-visitor name) (format #t "~a has come to visit ~a\n" name garden-name) (spawn ^visitor name))))
The garden community proxies a garden and passes along the name of the
gardener for the
dig-up methods so the garden
administrator can monitor what is going on. The remote editing
program was changed to accept a sturdy ref to a community and initiate
gardener registration via the
register-gardener message when it
starts. The graphical viewer was likewise modified to register a
visitor. (Note that there is no revocation feature to complement the
logging, so an administrator would be helpless to kick out a reckless
gardener! However, in a non-demo version, we could always add such a
How things went
Overall, I had a lot of fun learning to use the Goblins API. The way actors compose allows for building simple pieces that can be combined to form complex behavior. I didn’t know exactly what the architecture of my application would be when I started, but Goblins allowed me to start very small and build on a series of successful prototypes. Using actors across several vats within the same Guile process provided the foundation for introducing the networking layer later. And of course, building and testing actors within a Guile REPL made development move quickly. It was really nice to not have to think about networking right away, but it was even nicer to finally add networking without changing the application architecture much.
It’s still early days for Goblins, and not everything is perfect. I think the REPL experience could be improved. Wrapping every Goblins expression in a vat call feels cumbersome. Also, handling/debugging errors generated within a vat is less than ideal. The exceptions and backtraces I see are often hard to understand. During phase 3 I got stuck on a bug for awhile, thinking that I wasn’t understanding something fundamental to using actors over OCapN, when really I had introduced a simple argument ordering bug that was completely unrelated. I would like to help address these issues in the future.
I think games, even simple ones that barely do anything at all, are a great way to explore and learn new concepts. Building this very simple community garden simulator has taught me a lot about Goblins and has given me many ideas about what I could build in the future. You can find the full source code for this demo on GitLab.
I hope this post demonstrates how someone with no prior experience building applications using object capabilities can get started and have fun doing it! If you're excited to learn more, check out the The Heart of Spritely whitepaper!