Scuttlego
An implementation of the Secure Scuttlebutt protocol.
29 January 2023
boreq
boreq
Clients:
Implementations:
A new Secure Scuttlebutt implementation written in Go.
Reuses some elements of go-ssb:
"The structure and language of software code should match the business domain."
type Message struct {
Id string
Sequence int
}
func DoSomething(msg Message) error {
if msg.Id == "" {
return Message{}, errors.New("empty id")
}
if msg.Sequence <= 0 {
return Message{}, errors.New("sequence must be positive")
}
// do things
return nil
}
8
type Message struct {
id string
sequence int
}
func NewMessage(id string, sequence int) Message {
return Message{
id: id,
sequence: sequence,
}
}
func (msg Message) Id() string {
return msg.id
}
func (msg Message) Sequence() int {
return msg.sequence
}
9
type Message struct {
id string
sequence int
}
func NewMessage(id string, sequence int) (Message, error) {
if id == "" {
return Message{}, errors.New("empty id")
}
if sequence <= 0 {
return Message{}, errors.New("sequence must be positive")
}
return Message{
id: id,
sequence: sequence,
}, nil
}
func (msg Message) Id() string { return msg.id }
func (msg Message) Sequence() int { return msg.sequence }
10
type Message struct {
id Id
sequence Sequence
}
func NewMessage(id Id, sequence Sequence) Message {
return Message{
id: id,
sequence: sequence,
}
}
func (msg Message) Id() Id { return msg.id }
func (msg Message) Sequence() Sequence { return msg.sequence }
11
type Id struct {
id string
}
func NewId(id string) (Id, error) {
if id == "" {
return Id{}, errors.New("empty id")
}
return Id{
id: id,
}, nil
}
12
type Sequence struct {
sequence int
}
func NewSequence(sequence int) (Sequence, error) {
if sequence <= 0 {
return Sequence{}, errors.New("sequence must be positive")
}
return Sequence{
sequence: sequence,
}, nil
}
13
type Message struct {
id Id
sequence Sequence
}
func NewMessage(id Id, sequence Sequence) (Message, error) {
if id.IsZero() {
return Message{}, errors.New("zero value of id")
}
if sequence.IsZero() {
return Message{}, errors.New("zero value of sequence")
}
return Message{
id: id,
sequence: sequence,
}, nil
}
func (id Id) IsZero() bool { return id == Id{} }
func (seq Sequence) IsZero() bool { return seq == Sequence{} }
14
type Message struct {
// ...
}
type Feed struct {
Messages []Message
}
func AddToFeed(feed Feed, message Message) error {
// validate feed
// validate message
}
15
type Message struct {
// ...
}
type Feed struct {
messages []Message
}
func (f *Feed) AddToFeed(message Message) error {
if len(f.messages) > 0 {
// ...
if !f.lastMsg().ComesDirectlyBefore(message) {
return errors.New("this is not the next message in this feed")
}
} else {
if !message.IsRootMessage() {
return errors.New("first message in the feed must be a root message")
}
}
f.messages = append(f.messages, message)
return nil
}
16
type AppendMessage struct {
msg Message
}
type UpdateFeedFn func(f *domain.Feed) error
type FeedRepository interface {
UpdateFeed(id domain.FeedRef, fn UpdateFeedFn) error
}
type AppendMessageHandler struct {
repository FeedRepository
}
func NewAppendMessageHandler(repository FeedRepository) AppendMessageHandler {
return AppendMessageHandler{repository: repository}
}
func (h AppendMessageHandler) Handle(cmd AppendMessage) error {
return h.repository.UpdateFeed(cmd.msg.Feed(), func(f *domain.Feed) error {
return f.AppendMessage(cmd.msg)
})
}
17
In application layer:
type Commands struct {
AppendMessage *commands.AppendMessageHandler
}
type Queries struct {
GetMessage *queries.GetMessageHandler
}
type Application struct {
Commands Commands
Queries Queries
}
Outside of the application layer e.g. in ports:
func (h HTTPHandler) DoSomething(...) error {
cmd := commands.NewAppendMessage(...)
return h.app.Commands.AppendMessage.Handle(cmd)
}
18
Initially bbolt seemed like a good option.
Problem:
mmap allocate error: cannot allocate memory
// ...
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
// ...
Solution:
Well-tested domain layer prevents a lot of bugs and allows you to avoid writing component tests for anything other than complex behaviours.
Using github.com/stretchr/testify
is a good idea.
Table tests are a good idea.
Test fixtures e.g. SomeProcedureName
, SomeBool
, SomeDirectory
are useful.
Performance tailored for mobile devices:
Noticable performance improvements when using the app mostly due to:
https://github.com/planetary-social/scuttlego
MIT licensed.
22boreq