Skip to content

Commit

Permalink
Dev/jgough/10216 support eventsv1 (#41)
Browse files Browse the repository at this point in the history
* Update to add support for eventv1 events

re: AB#10216

---------

Co-authored-by: jgough <[email protected]>
  • Loading branch information
honourfish and jgough authored Jan 21, 2025
1 parent 5fa81b1 commit 9835c1c
Show file tree
Hide file tree
Showing 30 changed files with 1,455 additions and 1,003 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: '1.22'
go-version: '1.23'
cache: false

- name: Install Go quality tools
run: |
go install golang.org/x/tools/cmd/[email protected]
go install github.com/axw/gocov/[email protected]
go install github.com/jstemmer/go-junit-report/[email protected]
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.60.3
- name: Install Task
uses: arduino/setup-task@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func NewApp(version string, ikwid bool) *cli.App {
Usage: "common read only operations on datatrails merklelog verifiable data",
Flags: []cli.Flag{
&cli.StringFlag{Name: "loglevel", Value: "NOOP"},
&cli.Int64Flag{Name: "height", Value: 14, Usage: "override the massif height"},
&cli.Int64Flag{Name: "height", Value: int64(defaultMassifHeight), Usage: "override the massif height"},
&cli.StringFlag{
Name: "data-url", Aliases: []string{"u"},
Usage: "url to download merkle log data from. mutually exclusive with data-local; if neither option is supplied, DataTrails' live log data will be used",
Expand Down
199 changes: 199 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package app

import (
"bytes"
"encoding/json"
"errors"
"io"
"strings"

"github.com/datatrails/go-datatrails-logverification/logverification/app"
)

/**
* Merklelog related APP contents
*
* An APP in the context of merklelog is an interface that commits log entries.
*
* Apps include:
* - assetsv2
* - eventsv1
*/

const (
AssetsV2AppDomain = byte(0)
EventsV1AppDomain = byte(1)
)

var (
ErrNoJsonGiven = errors.New("no json given")
)

// AppDataToVerifiableLogEntries converts the app data (one or more app entries) to verifiable log entries
func AppDataToVerifiableLogEntries(appData []byte, logTenant string) ([]app.AppEntry, error) {

// first attempt to convert the appdata to a list of events
eventList, err := eventListFromJson(appData)
if err != nil {
return nil, err
}

// now we have an event list we can decipher if the app is
// assetsv2 or eventsv1
appDomain := appDomain(appData)

verifiableLogEntries := []app.AppEntry{}

switch appDomain {
case AssetsV2AppDomain:
// assetsv2
verfiableAssetsV2Events, err := NewAssetsV2AppEntries(eventList)
if err != nil {
return nil, err
}

verifiableLogEntries = append(verifiableLogEntries, verfiableAssetsV2Events...)

case EventsV1AppDomain:
verfiableEventsV1Events, err := NewEventsV1AppEntries(eventList, logTenant)
if err != nil {
return nil, err
}

verifiableLogEntries = append(verifiableLogEntries, verfiableEventsV1Events...)

default:
return nil, errors.New("unknown app domain for given app data")
}

return verifiableLogEntries, nil
}

// appDomain returns the app domain of the given app data
func appDomain(appData []byte) byte {

// first attempt to convert the appdata to a list of events
eventList, err := eventListFromJson(appData)
if err != nil {
// if we can't return default of assetsv2
return AssetsV2AppDomain
}

// decode into events
events := struct {
Events []json.RawMessage `json:"events,omitempty"`
NextPageToken json.RawMessage `json:"next_page_token,omitempty"`
}{}

decoder := json.NewDecoder(bytes.NewReader(eventList))
decoder.DisallowUnknownFields()
for {
err = decoder.Decode(&events)

if errors.Is(err, io.EOF) {
break
}

if err != nil {
// return default of assetsv2
return AssetsV2AppDomain
}
}

// decode the first event and find the identity
event := struct {
Identity string `json:"identity,omitempty"`
}{}

decoder = json.NewDecoder(bytes.NewReader(events.Events[0]))
decoder.DisallowUnknownFields()

for {
err = decoder.Decode(&event)

if errors.Is(err, io.EOF) {
break
}

if err != nil {
// if we can't return default of assetsv2
return AssetsV2AppDomain
}
}

// find if the event identity is assetsv2 or eventsv1 identity
if strings.HasPrefix(event.Identity, "assets/") {
return AssetsV2AppDomain
} else {
return EventsV1AppDomain
}

}

// eventListFromJson normalises a json encoded event or *list* of events, by
// always returning a list of json encoded events.
//
// This converts events from the following apps:
// - assetsv2
// - eventsv1
//
// NOTE: there is no json validation done on the event or list of events given
// any valid json will be accepted, use validation logic after this function.
func eventListFromJson(data []byte) ([]byte, error) {
var err error

doc := struct {
Events []json.RawMessage `json:"events,omitempty"`
NextPageToken json.RawMessage `json:"next_page_token,omitempty"`
}{}

// check for empty json
// NOTE: also len(nil) == 0, so does the nil check also
if len(data) == 0 {
return nil, ErrNoJsonGiven
}

decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()

for {

err = decoder.Decode(&doc)

// if we can decode the events json
// we know its in the form of a list events json response from
// the list events api, so just return data
if errors.Is(err, io.EOF) {
return data, nil
}

if err != nil {
break
}

}

// if we get here we know that the given data doesn't represent
// a list events json response
// so we can assume its a single event response from the events api.

var event json.RawMessage
err = json.Unmarshal(data, &event)
if err != nil {
return nil, err
}

// purposefully omit the next page token for response
listEvents := struct {
Events []json.RawMessage `json:"events,omitempty"`
}{}

listEvents.Events = []json.RawMessage{event}

events, err := json.Marshal(&listEvents)
if err != nil {
return nil, err
}

return events, nil
}
77 changes: 77 additions & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package app

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestEventListFromJson(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
expected []byte
wantErr bool
}{
{
name: "nil",
args: args{
data: nil,
},
expected: nil,
wantErr: true,
},
{
name: "empty",
args: args{
data: []byte{},
},
expected: nil,
wantErr: true,
},
// We do need this, since we expect input from other processes via pipes (i.e. an events query)
{
name: "empty list",
args: args{
data: []byte(`{"events":[]}`),
},
expected: []byte(`{"events":[]}`),
wantErr: false,
},
{
name: "single event",
args: args{
data: []byte(`{"identity":"assets/1/events/2"}`),
},
expected: []byte(`{"events":[{"identity":"assets/1/events/2"}]}`),
wantErr: false,
},
{
name: "single list",
args: args{
data: []byte(`{"events":[{"identity":"assets/1/events/2"}]}`),
},
expected: []byte(`{"events":[{"identity":"assets/1/events/2"}]}`),
wantErr: false,
},
{
name: "multiple list",
args: args{
data: []byte(`{"events":[{"identity":"assets/1/events/2"},{"identity":"assets/1/events/3"}]}`),
},
expected: []byte(`{"events":[{"identity":"assets/1/events/2"},{"identity":"assets/1/events/3"}]}`),
wantErr: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := eventListFromJson(test.args.data)

assert.Equal(t, test.wantErr, err != nil)
assert.Equal(t, test.expected, actual)
})
}
}
Loading

0 comments on commit 9835c1c

Please sign in to comment.