diff --git a/logbook/book.go b/logbook/book.go index 28f2e2771..b4461892a 100644 --- a/logbook/book.go +++ b/logbook/book.go @@ -1,15 +1,15 @@ package log import ( + "context" "fmt" + "io/ioutil" "time" - flatbuffers "github.com/google/flatbuffers/go" crypto "github.com/libp2p/go-libp2p-crypto" "github.com/qri-io/dataset" "github.com/qri-io/qfs" "github.com/qri-io/qri/logbook/log" - "github.com/qri-io/qri/logbook/logfb" "github.com/qri-io/qri/repo" ) @@ -21,21 +21,11 @@ const ( aclModel uint32 = 0x0005 ) -// Book is a journal of operations organized into a collection of append-only -// logs. Each log is single-writer -// Books are connected to a single author, and represent their view of -// the global dataset graph. -// Any write operation performed on the logbook are attributed to a single -// author, denoted by a private key. Books can replicate logs from other -// authors, forming a conflict-free replicated data type (CRDT), and a basis -// for collaboration through knowledge of each other's operations +// Book wraps a log.Book with a higher-order API specific to Qri type Book struct { - username string - id string - pk crypto.PrivKey - // modelSets map[uint32]*log.Set - authors []*log.Set - datasets []*log.Set + book log.Book + location string + fs qfs.Filesystem } // NewBook initializes a logbook, reading any existing data at the given @@ -57,6 +47,40 @@ func (book Book) DeleteAuthor() error { return fmt.Errorf("not finished") } +// Save writes the +func (book Book) Save(ctx context.Context) error { + ciphertext, err := book.book.(book.flatbufferBytes()) + if err != nil { + return err + } + + file := qfs.NewMemfileBytes(book.location, ciphertext) + return book.fs.Put(ctx, file) +} + +// Load +func (book Book) Load(ctx context.Context) error { + f, err := book.fs.Get(ctx, book.location) + if err != nil { + if err == qfs.ErrNotFound { + return nil + } + return err + } + + ciphertext, err := ioutil.ReadAll(f) + if err != nil { + return err + } + + plaintext, err := book.decrypt(ciphertext) + if err != nil { + return err + } + + return +} + // NameInit initializes a new name within the author's namespace. Dataset // histories start with a NameInit func (book Book) NameInit(name string) error { @@ -144,77 +168,3 @@ func (book Book) readAlias(alias string) (*log.Log, error) { } return nil, fmt.Errorf("not finished") } - -func (book Book) save() error { - return fmt.Errorf("not finished") -} - -// flatbufferBytes formats book as a flatbuffer byte slice -func (book Book) flatbufferBytes() []byte { - builder := flatbuffers.NewBuilder(0) - off := book.marshalFlatbuffer(builder) - builder.Finish(off) - return builder.FinishedBytes() -} - -func (book Book) marshalFlatbuffer(builder *flatbuffers.Builder) flatbuffers.UOffsetT { - username := builder.CreateString(book.username) - id := builder.CreateString(book.id) - - count := len(book.authors) - offsets := make([]flatbuffers.UOffsetT, count) - for i, lset := range book.authors { - offsets[i] = lset.MarshalFlatbuffer(builder) - } - logfb.BookStartAuthorsVector(builder, count) - for i := count - 1; i >= 0; i-- { - builder.PrependUOffsetT(offsets[i]) - } - authors := builder.EndVector(count) - - count = len(book.datasets) - offsets = make([]flatbuffers.UOffsetT, count) - for i, lset := range book.datasets { - offsets[i] = lset.MarshalFlatbuffer(builder) - } - logfb.BookStartAuthorsVector(builder, count) - for i := count - 1; i >= 0; i-- { - builder.PrependUOffsetT(offsets[i]) - } - datasets := builder.EndVector(count) - - logfb.BookStart(builder) - logfb.BookAddName(builder, username) - logfb.BookAddIdentifier(builder, id) - logfb.BookAddAuthors(builder, authors) - logfb.BookAddDatasets(builder, datasets) - return logfb.BookEnd(builder) -} - -func (book *Book) unmarshalFlatbuffer(b *logfb.Book) error { - newBook := Book{} - - newBook.authors = make([]*log.Set, b.AuthorsLength()) - var logsetfb logfb.Logset - for i := 0; i < b.AuthorsLength(); i++ { - if b.Authors(&logsetfb, i) { - newBook.authors[i] = &log.Set{} - if err := newBook.authors[i].UnmarshalFlatbuffer(&logsetfb); err != nil { - return err - } - } - } - - newBook.datasets = make([]*log.Set, b.DatasetsLength()) - for i := 0; i < b.DatasetsLength(); i++ { - if ok := b.Datasets(&logsetfb, i); ok { - newBook.datasets[i] = &log.Set{} - if err := newBook.datasets[i].UnmarshalFlatbuffer(&logsetfb); err != nil { - return err - } - } - } - - *book = newBook - return nil -} diff --git a/logbook/log.fbs b/logbook/log/log.fbs similarity index 97% rename from logbook/log.fbs rename to logbook/log/log.fbs index 35ee3feb7..d6cd725c0 100644 --- a/logbook/log.fbs +++ b/logbook/log/log.fbs @@ -63,8 +63,7 @@ table Logset { table Book { name:string; // book author name identifier:string; // book author identifier - authors:[Logset]; // list of author keys - datasets:[Logset]; // named dataset logs + sets:[Logset]; // collection of logsets in this book } root_type Book; \ No newline at end of file diff --git a/logbook/log/log.go b/logbook/log/log.go index 8ad114874..3e8f42e8e 100644 --- a/logbook/log/log.go +++ b/logbook/log/log.go @@ -1,12 +1,171 @@ package log import ( + "context" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/rand" + "encoding/hex" "fmt" + "io" flatbuffers "github.com/google/flatbuffers/go" - "github.com/qri-io/qri/logbook/logfb" + crypto "github.com/libp2p/go-libp2p-crypto" + "github.com/qri-io/qri/logbook/log/logfb" ) +// Book is a journal of operations organized into a collection of append-only +// logs. Each log is single-writer +// Books are connected to a single author, and represent their view of +// the global dataset graph. +// Any write operation performed on the logbook are attributed to a single +// author, denoted by a private key. Books can replicate logs from other +// authors, forming a conflict-free replicated data type (CRDT), and a basis +// for collaboration through knowledge of each other's operations +type Book struct { + authorname string + id string + pk crypto.PrivKey + sets map[uint32][]*Set +} + +// NewBook initializes a Book +func NewBook(pk crypto.PrivKey, authorname, authorID string) (*Book, error) { + return &Book{ + pk: pk, + authorname: authorname, + sets: map[uint32][]*Set{}, + }, nil +} + +// AppendSet adds a set to a book +func (book *Book) AppendSet(set *Set) { + book.sets[set.Model()] = append(book.sets[set.Model()], set) +} + +// UnmarshalFlatbufferCipher decrypts and loads a flatbuffer ciphertext +func (book *Book) UnmarshalFlatbufferCipher(ctx context.Context, ciphertext []byte) error { + plaintext, err := book.decrypt(ciphertext) + if err != nil { + return err + } + + return book.unmarshalFlatbuffer(logfb.GetRootAsBook(plaintext, 0)) +} + +// FlatbufferCipher marshals book to a flatbuffer and encrypts the book using +// the book private key +func (book Book) FlatbufferCipher() ([]byte, error) { + return book.encrypt(book.flatbufferBytes()) +} + +func (book Book) cipher() (cipher.AEAD, error) { + pkBytes, err := book.pk.Raw() + if err != nil { + return nil, err + } + hasher := md5.New() + hasher.Write(pkBytes) + hash := hex.EncodeToString(hasher.Sum(nil)) + + block, err := aes.NewCipher([]byte(hash)) + if err != nil { + return nil, err + } + return cipher.NewGCM(block) +} + +func (book Book) encrypt(data []byte) ([]byte, error) { + gcm, err := book.cipher() + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + ciphertext := gcm.Seal(nonce, nonce, data, nil) + return ciphertext, nil +} + +func (book Book) decrypt(data []byte) ([]byte, error) { + gcm, err := book.cipher() + if err != nil { + return nil, err + } + + nonceSize := gcm.NonceSize() + nonce, ciphertext := data[:nonceSize], data[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + return plaintext, nil +} + +// flatbufferBytes formats book as a flatbuffer byte slice +func (book Book) flatbufferBytes() []byte { + builder := flatbuffers.NewBuilder(0) + off := book.marshalFlatbuffer(builder) + builder.Finish(off) + return builder.FinishedBytes() +} + +func (book Book) marshalFlatbuffer(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + authorname := builder.CreateString(book.authorname) + id := builder.CreateString(book.id) + + setsl := book.setsSlice() + count := len(setsl) + offsets := make([]flatbuffers.UOffsetT, count) + for i, lset := range setsl { + offsets[i] = lset.MarshalFlatbuffer(builder) + } + logfb.BookStartSetsVector(builder, count) + for i := count - 1; i >= 0; i-- { + builder.PrependUOffsetT(offsets[i]) + } + sets := builder.EndVector(count) + + logfb.BookStart(builder) + logfb.BookAddName(builder, authorname) + logfb.BookAddIdentifier(builder, id) + logfb.BookAddSets(builder, sets) + return logfb.BookEnd(builder) +} + +func (book *Book) unmarshalFlatbuffer(b *logfb.Book) error { + newBook := Book{ + id: string(b.Identifier()), + sets: map[uint32][]*Set{}, + } + + count := b.SetsLength() + logsetfb := &logfb.Logset{} + for i := 0; i < count; i++ { + if b.Sets(logsetfb, i) { + set := &Set{} + if err := set.UnmarshalFlatbuffer(logsetfb); err != nil { + return err + } + newBook.sets[set.Model()] = append(newBook.sets[set.Model()], set) + } + } + + *book = newBook + return nil +} + +func (book Book) setsSlice() (sets []*Set) { + for _, setsl := range book.sets { + sets = append(sets, setsl...) + } + return sets +} + // Set is a collection of logs type Set struct { signer string @@ -26,12 +185,28 @@ func InitSet(name string, initop Op) *Set { } } +// NewSet creates a set from a given log, rooted at the set name +func NewSet(lg *Log) *Set { + name := lg.Name() + return &Set{ + root: name, + logs: map[string]*Log{ + name: lg, + }, + } +} + // Author gives authorship information about who created this logset func (ls Set) Author() (string, string) { // TODO (b5) - fetch from master branch intiailization return "", "" } +// Model returns the model of the root log +func (ls Set) Model() uint32 { + return ls.logs[ls.root].ops[0].Model +} + // MarshalFlatbuffer writes the set to a flatbuffer builder func (ls Set) MarshalFlatbuffer(builder *flatbuffers.Builder) flatbuffers.UOffsetT { namestr, idstr := ls.Author() @@ -97,6 +272,11 @@ func InitLog(initop Op) *Log { } } +// Append adds an operation to the log +func (lg *Log) Append(op Op) { + lg.ops = append(lg.ops, op) +} + // Len returns the number of of the latest entry in the log func (lg Log) Len() int { return len(lg.ops) @@ -138,8 +318,8 @@ func (lg Log) Verify() error { return fmt.Errorf("not finished") } -// MarshalFlatbuffer writes log to a flatbuffer, returning the -// ending byte offset +// MarshalFlatbuffer writes log to a flatbuffer, returning the ending byte +// offset func (lg Log) MarshalFlatbuffer(builder *flatbuffers.Builder) flatbuffers.UOffsetT { namestr, idstr := lg.Author() name := builder.CreateString(namestr) @@ -202,6 +382,8 @@ const ( OpTypeRemove OpType = 0x03 ) +// Op is an operation, a single atomic unit in a log that describes a state +// change type Op struct { Type OpType // type of operation Model uint32 // data model to operate on diff --git a/logbook/log/log_test.go b/logbook/log/log_test.go index 3f3aa14c6..a74616335 100644 --- a/logbook/log/log_test.go +++ b/logbook/log/log_test.go @@ -1,56 +1,140 @@ package log import ( + "bytes" + "context" + "encoding/base64" + "fmt" "testing" - flatbuffers "github.com/google/flatbuffers/go" "github.com/google/go-cmp/cmp" - "github.com/qri-io/qri/logbook/logfb" + crypto "github.com/libp2p/go-libp2p-crypto" + "github.com/qri-io/qri/logbook/log/logfb" ) var allowUnexported = cmp.AllowUnexported( + Book{}, Set{}, Log{}, Op{}, ) -func TestAllOpsLogsetFlatbuffer(t *testing.T) { - - set := &Set{ - logs: map[string]*Log{ - "steve": &Log{ - signature: nil, - ops: []Op{ - Op{ - Type: OpTypeInit, - Model: 0x0001, - Ref: "QmRefHash", - Prev: "QmPrevHash", - Relations: []string{"a", "b", "c"}, - Name: "steve", - AuthorID: "QmSteveHash", - Timestamp: 1, - Size: 2, - Note: "note!", - }, - }, - }, +func TestBookFlatbuffer(t *testing.T) { + set := InitSet("steve", Op{ + Type: OpTypeInit, + Model: 0x0001, + Ref: "QmRefHash", + Prev: "QmPrevHash", + Relations: []string{"a", "b", "c"}, + Name: "steve", + AuthorID: "QmSteveHash", + Timestamp: 1, + Size: 2, + Note: "note!", + }) + + book := &Book{ + sets: map[uint32][]*Set{ + 0x0001: []*Set{set}, }, } - builder := flatbuffers.NewBuilder(0) - off := set.MarshalFlatbuffer(builder) - builder.Finish(off) - - data := builder.FinishedBytes() - logsetfb := logfb.GetRootAsLogset(data, 0) + data := book.flatbufferBytes() + logsetfb := logfb.GetRootAsBook(data, 0) - got := &Set{} - if err := got.UnmarshalFlatbuffer(logsetfb); err != nil { + got := &Book{} + if err := got.unmarshalFlatbuffer(logsetfb); err != nil { t.Fatalf("unmarshalling flatbuffer bytes: %s", err.Error()) } - if diff := cmp.Diff(set, got, allowUnexported); diff != "" { + if diff := cmp.Diff(book, got, allowUnexported); diff != "" { t.Errorf("result mismatch (-want +got):\n%s", diff) } } + +func TestBookCiphertext(t *testing.T) { + tr, cleanup := newTestRunner(t) + defer cleanup() + + lg := tr.RandomLog(Op{ + Type: OpTypeInit, + Model: 0x0001, + Name: "apples", + }, 10) + + book := tr.Book + book.AppendSet(NewSet(lg)) + + gotcipher, err := book.FlatbufferCipher() + if err != nil { + t.Fatalf("calculating flatbuffer cipher: %s", err.Error()) + } + + plaintext := book.flatbufferBytes() + if bytes.Equal(gotcipher, plaintext) { + t.Errorf("plaintext bytes & ciphertext bytes can't be equal") + } + + // TODO (b5) - we should confirm the ciphertext isn't readable, but + // this'll panic with out-of-bounds slice access... + // ciphertextAsBook := logfb.GetRootAsBook(gotcipher, 0) + // if err := book.unmarshalFlatbuffer(ciphertextAsBook); err == nil { + // t.Errorf("ciphertext as book should not have worked") + // } + + if err = book.UnmarshalFlatbufferCipher(tr.Ctx, gotcipher); err != nil { + t.Errorf("book.UnmarhsalFlatbufferCipher unexpected error: %s", err.Error()) + } +} + +type testRunner struct { + Ctx context.Context + AuthorName string + AuthorID string + Book *Book + gen *opGenerator +} + +func newTestRunner(t *testing.T) (tr testRunner, cleanup func()) { + ctx := context.Background() + authorName := "test_author" + authorID := "QmTestAuthorID" + // logbooks are encrypted at rest, we need a private key to interact with + // them, including to create a new logbook. This is a dummy Private Key + // you should never, ever use in real life. demo only folks. + testPk := `CAASpgkwggSiAgEAAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAECggEAaVOxe6Y5A5XzrxHBDtzjlwcBels3nm/fWScvjH4dMQXlavwcwPgKhy2NczDhr4X69oEw6Msd4hQiqJrlWd8juUg6vIsrl1wS/JAOCS65fuyJfV3Pw64rWbTPMwO3FOvxj+rFghZFQgjg/i45uHA2UUkM+h504M5Nzs6Arr/rgV7uPGR5e5OBw3lfiS9ZaA7QZiOq7sMy1L0qD49YO1ojqWu3b7UaMaBQx1Dty7b5IVOSYG+Y3U/dLjhTj4Hg1VtCHWRm3nMOE9cVpMJRhRzKhkq6gnZmni8obz2BBDF02X34oQLcHC/Wn8F3E8RiBjZDI66g+iZeCCUXvYz0vxWAQQKBgQDEJu6flyHPvyBPAC4EOxZAw0zh6SF/r8VgjbKO3n/8d+kZJeVmYnbsLodIEEyXQnr35o2CLqhCvR2kstsRSfRz79nMIt6aPWuwYkXNHQGE8rnCxxyJmxV4S63GczLk7SIn4KmqPlCI08AU0TXJS3zwh7O6e6kBljjPt1mnMgvr3QKBgQD6fAkdI0FRZSXwzygx4uSg47Co6X6ESZ9FDf6ph63lvSK5/eue/ugX6p/olMYq5CHXbLpgM4EJYdRfrH6pwqtBwUJhlh1xI6C48nonnw+oh8YPlFCDLxNG4tq6JVo071qH6CFXCIank3ThZeW5a3ZSe5pBZ8h4bUZ9H8pJL4C7yQKBgFb8SN/+/qCJSoOeOcnohhLMSSD56MAeK7KIxAF1jF5isr1TP+rqiYBtldKQX9bIRY3/8QslM7r88NNj+aAuIrjzSausXvkZedMrkXbHgS/7EAPflrkzTA8fyH10AsLgoj/68mKr5bz34nuY13hgAJUOKNbvFeC9RI5g6eIqYH0FAoGAVqFTXZp12rrK1nAvDKHWRLa6wJCQyxvTU8S1UNi2EgDJ492oAgNTLgJdb8kUiH0CH0lhZCgr9py5IKW94OSM6l72oF2UrS6PRafHC7D9b2IV5Al9lwFO/3MyBrMocapeeyaTcVBnkclz4Qim3OwHrhtFjF1ifhP9DwVRpuIg+dECgYANwlHxLe//tr6BM31PUUrOxP5Y/cj+ydxqM/z6papZFkK6Mvi/vMQQNQkh95GH9zqyC5Z/yLxur4ry1eNYty/9FnuZRAkEmlUSZ/DobhU0Pmj8Hep6JsTuMutref6vCk2n02jc9qYmJuD7iXkdXDSawbEG6f5C4MUkJ38z1t1OjA==` + data, err := base64.StdEncoding.DecodeString(testPk) + if err != nil { + panic(err) + } + pk, err := crypto.UnmarshalPrivateKey(data) + if err != nil { + panic(fmt.Errorf("error unmarshaling private key: %s", err.Error())) + } + + book, err := NewBook(pk, authorName, authorID) + if err != nil { + t.Fatalf("creating book: %s", err.Error()) + } + + tr = testRunner{ + Ctx: ctx, + AuthorName: authorName, + AuthorID: authorID, + Book: book, + gen: &opGenerator{ctx: ctx, NoopProb: 60}, + } + cleanup = func() { + // noop + } + + return tr, cleanup +} + +func (tr testRunner) RandomLog(init Op, opCount int) *Log { + lg := InitLog(init) + for i := 0; i < opCount; i++ { + lg.Append(tr.gen.Gen()) + } + return lg +} diff --git a/logbook/logfb/Book.go b/logbook/log/logfb/Book.go similarity index 60% rename from logbook/logfb/Book.go rename to logbook/log/logfb/Book.go index b8ddc38ec..8272c6d38 100644 --- a/logbook/logfb/Book.go +++ b/logbook/log/logfb/Book.go @@ -42,7 +42,7 @@ func (rcv *Book) Identifier() []byte { return nil } -func (rcv *Book) Authors(obj *Logset, j int) bool { +func (rcv *Book) Sets(obj *Logset, j int) bool { o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) if o != 0 { x := rcv._tab.Vector(o) @@ -54,7 +54,7 @@ func (rcv *Book) Authors(obj *Logset, j int) bool { return false } -func (rcv *Book) AuthorsLength() int { +func (rcv *Book) SetsLength() int { o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) if o != 0 { return rcv._tab.VectorLen(o) @@ -62,28 +62,8 @@ func (rcv *Book) AuthorsLength() int { return 0 } -func (rcv *Book) Datasets(obj *Logset, j int) bool { - o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) - if o != 0 { - x := rcv._tab.Vector(o) - x += flatbuffers.UOffsetT(j) * 4 - x = rcv._tab.Indirect(x) - obj.Init(rcv._tab.Bytes, x) - return true - } - return false -} - -func (rcv *Book) DatasetsLength() int { - o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) - if o != 0 { - return rcv._tab.VectorLen(o) - } - return 0 -} - func BookStart(builder *flatbuffers.Builder) { - builder.StartObject(4) + builder.StartObject(3) } func BookAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) { builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(name), 0) @@ -91,16 +71,10 @@ func BookAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) { func BookAddIdentifier(builder *flatbuffers.Builder, identifier flatbuffers.UOffsetT) { builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(identifier), 0) } -func BookAddAuthors(builder *flatbuffers.Builder, authors flatbuffers.UOffsetT) { - builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(authors), 0) -} -func BookStartAuthorsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { - return builder.StartVector(4, numElems, 4) -} -func BookAddDatasets(builder *flatbuffers.Builder, datasets flatbuffers.UOffsetT) { - builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(datasets), 0) +func BookAddSets(builder *flatbuffers.Builder, sets flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(sets), 0) } -func BookStartDatasetsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { +func BookStartSetsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { return builder.StartVector(4, numElems, 4) } func BookEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { diff --git a/logbook/logfb/Log.go b/logbook/log/logfb/Log.go similarity index 100% rename from logbook/logfb/Log.go rename to logbook/log/logfb/Log.go diff --git a/logbook/logfb/Logset.go b/logbook/log/logfb/Logset.go similarity index 100% rename from logbook/logfb/Logset.go rename to logbook/log/logfb/Logset.go diff --git a/logbook/logfb/OpType.go b/logbook/log/logfb/OpType.go similarity index 100% rename from logbook/logfb/OpType.go rename to logbook/log/logfb/OpType.go diff --git a/logbook/logfb/Operation.go b/logbook/log/logfb/Operation.go similarity index 100% rename from logbook/logfb/Operation.go rename to logbook/log/logfb/Operation.go diff --git a/logbook/log/simulate_test.go b/logbook/log/simulate_test.go index 2fbdee8a4..50b13478e 100644 --- a/logbook/log/simulate_test.go +++ b/logbook/log/simulate_test.go @@ -82,7 +82,7 @@ type opGenerator struct { opsGenerated int } -func (og *opGenerator) Gen(id int) Op { +func (og *opGenerator) MaybeGen(id int) Op { var o Op i := rand.Intn(100) if i > og.NoopProb { @@ -94,6 +94,17 @@ func (og *opGenerator) Gen(id int) Op { return o } +func (og *opGenerator) Gen() Op { + // Author: fmt.Sprintf("%d", id) + o := Op{ + Ref: fmt.Sprintf("%d", og.opsGenerated), + Note: fmt.Sprintf("op number %d", og.opsGenerated), + } + og.opsGenerated++ + + return o +} + type Peer struct { ID int Log Log @@ -130,7 +141,7 @@ func (p *Peer) Tick(t int) { return } - op := p.ops.Gen(p.ID) + op := p.ops.MaybeGen(p.ID) // TODO (b5) - need to restore with a new mechanism for put // p.Log = p.Log.Put(op) p.msgsSent++