diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 6f6a377..ebbc509 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -16,7 +16,7 @@ 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 @@ -24,7 +24,8 @@ jobs: go install golang.org/x/tools/cmd/goimports@v0.1.7 go install github.com/axw/gocov/gocov@v1.1.0 go install github.com/jstemmer/go-junit-report/v2@v2.1.0 - 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: diff --git a/app.go b/app.go index aa7060b..1f7ec17 100644 --- a/app.go +++ b/app.go @@ -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", diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..1350173 --- /dev/null +++ b/app/app.go @@ -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 +} diff --git a/app/app_test.go b/app/app_test.go new file mode 100644 index 0000000..10cda5e --- /dev/null +++ b/app/app_test.go @@ -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) + }) + } +} diff --git a/app/assetsv2.go b/app/assetsv2.go new file mode 100644 index 0000000..e14bb46 --- /dev/null +++ b/app/assetsv2.go @@ -0,0 +1,129 @@ +package app + +import ( + "encoding/json" + "errors" + "sort" + "strings" + + "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" + "github.com/datatrails/go-datatrails-logverification/logverification" + "github.com/datatrails/go-datatrails-logverification/logverification/app" + "github.com/google/uuid" + "google.golang.org/protobuf/encoding/protojson" +) + +var ( + ErrInvalidAssetsV2EventJson = errors.New(`invalid assetsv2 event json`) + ErrNoEvents = errors.New(`no events found in events json`) +) + +func VerifiableAssetsV2EventsFromData(data []byte) ([]app.AppEntry, error) { + + // Accept either the list events response format or a single event. Peak + // into the json data to pick which. + eventsJson, err := eventListFromJson(data) + if err != nil { + return nil, err + } + + verifiableEvents, err := NewAssetsV2AppEntries(eventsJson) + if err != nil { + return nil, err + } + + for _, event := range verifiableEvents { + validationErr := logverification.Validate(event) + if validationErr != nil { + return nil, validationErr + } + } + + return verifiableEvents, nil +} + +// NewAssetsV2AppEntries takes a list of events JSON (e.g. from the assetsv2 events list API), converts them +// into AssetsV2AppEntries and then returns them sorted by ascending MMR index. +func NewAssetsV2AppEntries(eventsJson []byte) ([]app.AppEntry, error) { + // get the event list out of events + eventListJson := struct { + Events []json.RawMessage `json:"events"` + }{} + + err := json.Unmarshal(eventsJson, &eventListJson) + if err != nil { + return nil, err + } + + events := []app.AppEntry{} + for _, eventJson := range eventListJson.Events { + verifiableEvent, err := NewAssetsV2AppEntry(eventJson) + if err != nil { + return nil, err + } + + events = append(events, *verifiableEvent) + } + + // check if we haven't got any events + if len(events) == 0 { + return nil, ErrNoEvents + } + + // Sorting the events by MMR index guarantees that they're sorted in log append order. + sort.Slice(events, func(i, j int) bool { + return events[i].MMRIndex() < events[j].MMRIndex() + }) + + return events, nil +} + +// NewAssetsV2AppEntry takes a single assetsv2 event JSON and returns an AssetsV2AppEntry, +// providing just enough information to verify the incluson of and identify the event. +func NewAssetsV2AppEntry(eventJson []byte) (*app.AppEntry, error) { + + // special care is needed here to deal with uint64 types. json marshal / + // un marshal treats them as strings because they don't fit in a + // javascript Number + + // Unmarshal into a generic type to get just the bits we need. Use + // defered decoding to get the raw merklelog entry as it must be + // unmarshaled using protojson and the specific generated target type. + entry := struct { + Identity string `json:"identity,omitempty"` + TenantIdentity string `json:"tenant_identity,omitempty"` + // Note: the proof_details top level field can be ignored here because it is a 'oneof' + MerklelogEntry json.RawMessage `json:"merklelog_entry,omitempty"` + }{} + err := json.Unmarshal(eventJson, &entry) + if err != nil { + return nil, err + } + + merkleLog := &assets.MerkleLogEntry{} + err = protojson.Unmarshal(entry.MerklelogEntry, merkleLog) + if err != nil { + return nil, err + } + + if entry.TenantIdentity == "" { + return nil, ErrInvalidAssetsV2EventJson + } + + // get the logID from the event log tenant + logUuid := strings.TrimPrefix(entry.TenantIdentity, "tenant/") + logId, err := uuid.Parse(logUuid) + if err != nil { + return nil, err + } + + return app.NewAppEntry( + entry.Identity, + logId[:], + app.NewMMREntryFields( + byte(0), + eventJson, // we cheat a bit here, because the eventJson isn't really serialized, but its a log version 0 log entry + ), + merkleLog.Commit.Index, + ), nil +} diff --git a/app/assetsv2_test.go b/app/assetsv2_test.go new file mode 100644 index 0000000..bb7f765 --- /dev/null +++ b/app/assetsv2_test.go @@ -0,0 +1,95 @@ +package app + +import ( + "encoding/json" + "testing" + + "github.com/datatrails/go-datatrails-logverification/logverification/app" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVerifiableAssetsV2EventsFromData(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + args args + expected []app.AppEntry + err error + }{ + { + name: "empty event list", + args: args{ + data: []byte(`{"events":[]}`), + }, + expected: []app.AppEntry{}, + err: ErrNoEvents, + }, + { + name: "list with invalid v3 event returns a validation error", + args: args{ + data: []byte(`{ + "events":[ + { + "merklelog_entry": { + "commit": { + "index": "0", + "idtimestamp": "018e3f48610b089800" + } + } + } + ] +}`), + }, + expected: nil, + err: ErrInvalidAssetsV2EventJson, + }, + { + name: "single event list", + args: args{ + data: singleAssetsv2EventJsonList, + }, + expected: []app.AppEntry{ + *app.NewAppEntry( + "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", // app id + []byte{0x73, 0xb0, 0x6b, 0x4e, 0x50, 0x4e, 0x4d, 0x31, 0x9f, 0xd9, 0x5e, 0x60, 0x6f, 0x32, 0x9b, 0x51}, // log id + app.NewMMREntryFields( + byte(0), // domain + assetsv2EventJson, // serialized bytes + ), + 0, // mmr index + ), + }, + err: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual, err := VerifiableAssetsV2EventsFromData(test.args.data) + + assert.Equal(t, test.err, err) + assert.Equal(t, len(test.expected), len(actual)) + + for index, expectedEvent := range test.expected { + actualEvent := actual[index] + + assert.Equal(t, expectedEvent.AppID(), actualEvent.AppID()) + assert.Equal(t, expectedEvent.LogID(), actualEvent.LogID()) + assert.Equal(t, expectedEvent.MMRIndex(), actualEvent.MMRIndex()) + + // serialized bytes needs to be marshalled to show the json is equal for assetsv2 + var expectedJson map[string]any + err := json.Unmarshal(expectedEvent.SerializedBytes(), &expectedJson) + require.NoError(t, err) + + var actualJson map[string]any + err = json.Unmarshal(actualEvent.SerializedBytes(), &actualJson) + require.NoError(t, err) + + assert.Equal(t, expectedJson, actualJson) + } + }) + } +} diff --git a/app/consts_test.go b/app/consts_test.go new file mode 100644 index 0000000..682f958 --- /dev/null +++ b/app/consts_test.go @@ -0,0 +1,143 @@ +package app + +/** + * file for all the test constants + */ + +var ( + assetsv2EventJson = []byte(`{ + "identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", + "asset_identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8", + "event_attributes": {}, + "asset_attributes": { + "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee" + }, + "operation": "NewAsset", + "behaviour": "AssetCreator", + "timestamp_declared": "2024-03-14T23:24:50Z", + "timestamp_accepted": "2024-03-14T23:24:50Z", + "timestamp_committed": "2024-03-22T11:13:55.557Z", + "principal_declared": { + "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", + "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", + "display_name": "Root", + "email": "" + }, + "principal_accepted": { + "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", + "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", + "display_name": "Root", + "email": "" + }, + "confirmation_status": "CONFIRMED", + "transaction_id": "", + "block_number": 0, + "transaction_index": 0, + "from": "0xF17B3B9a3691846CA0533Ce01Fa3E35d6d6f714C", + "tenant_identity": "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", + "merklelog_entry": { + "commit": { + "index": "0", + "idtimestamp": "018e3f48610b089800" + }, + "confirm": { + "mmr_size": "7", + "root": "XdcejozGdFYn7JTa/5PUodWtmomUuGuTTouMvxyDevo=", + "timestamp": "1711106035557", + "idtimestamp": "", + "signed_tree_head": "" + }, + "unequivocal": null + } +}`) + + eventsv1EventJson = []byte(` +{ + "identity": "events/01947000-3456-780f-bfa9-29881e3bac88", + "attributes": { + "foo": "bar" + }, + "trails": [], + "origin_tenant": "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + "created_by": "2ef471c2-f997-4503-94c8-60b5c929a3c3", + "created_at": 1737045849174, + "confirmation_status": "CONFIRMED", + "merklelog_commit": { + "index": "1", + "idtimestamp": "019470003611017900" + } +} +`) + + singleEventsv1EventJsonList = []byte(` +{ + "events":[ + { + "identity": "events/01947000-3456-780f-bfa9-29881e3bac88", + "attributes": { + "foo": "bar" + }, + "trails": [], + "origin_tenant": "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + "created_by": "2ef471c2-f997-4503-94c8-60b5c929a3c3", + "created_at": 1737045849174, + "confirmation_status": "CONFIRMED", + "merklelog_commit": { + "index": "1", + "idtimestamp": "019470003611017900" + } + } + ] +}`) + + singleAssetsv2EventJsonList = []byte(` +{ + "events":[ + { + "identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", + "asset_identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8", + "event_attributes": {}, + "asset_attributes": { + "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee" + }, + "operation": "NewAsset", + "behaviour": "AssetCreator", + "timestamp_declared": "2024-03-14T23:24:50Z", + "timestamp_accepted": "2024-03-14T23:24:50Z", + "timestamp_committed": "2024-03-22T11:13:55.557Z", + "principal_declared": { + "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", + "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", + "display_name": "Root", + "email": "" + }, + "principal_accepted": { + "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", + "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", + "display_name": "Root", + "email": "" + }, + "confirmation_status": "CONFIRMED", + "transaction_id": "", + "block_number": 0, + "transaction_index": 0, + "from": "0xF17B3B9a3691846CA0533Ce01Fa3E35d6d6f714C", + "tenant_identity": "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", + "merklelog_entry": { + "commit": { + "index": "0", + "idtimestamp": "018e3f48610b089800" + }, + "confirm": { + "mmr_size": "7", + "root": "XdcejozGdFYn7JTa/5PUodWtmomUuGuTTouMvxyDevo=", + "timestamp": "1711106035557", + "idtimestamp": "", + "signed_tree_head": "" + }, + "unequivocal": null + } + } + ] +}`) +) diff --git a/app/eventsv1.go b/app/eventsv1.go new file mode 100644 index 0000000..8801e84 --- /dev/null +++ b/app/eventsv1.go @@ -0,0 +1,137 @@ +package app + +import ( + "encoding/json" + "errors" + "sort" + "strings" + + "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" + "github.com/datatrails/go-datatrails-logverification/logverification/app" + "github.com/datatrails/go-datatrails-serialization/eventsv1" + "github.com/google/uuid" + "google.golang.org/protobuf/encoding/protojson" +) + +var ( + ErrInvalidEventsV1EventJson = errors.New(`invalid eventsv1 event json`) +) + +func VerifiableEventsV1EventsFromData(data []byte, logTenant string) ([]app.AppEntry, error) { + + // Accept either the list events response format or a single event. Peak + // into the json data to pick which. + eventsJson, err := eventListFromJson(data) + if err != nil { + return nil, err + } + + verifiableEvents, err := NewEventsV1AppEntries(eventsJson, logTenant) + if err != nil { + return nil, err + } + + return verifiableEvents, nil +} + +// NewEventsV1AppEntries takes a list of events JSON (e.g. from the events list API), converts them +// into EventsV1AppEntries and then returns them sorted by ascending MMR index. +func NewEventsV1AppEntries(eventsJson []byte, logTenant string) ([]app.AppEntry, error) { + // get the event list out of events + eventListJson := struct { + Events []json.RawMessage `json:"events"` + }{} + + err := json.Unmarshal(eventsJson, &eventListJson) + if err != nil { + return nil, err + } + + // check if we haven't got any events + if len(eventListJson.Events) == 0 { + return nil, ErrNoEvents + } + + events := []app.AppEntry{} + for _, eventJson := range eventListJson.Events { + verifiableEvent, err := NewEventsV1AppEntry(eventJson, logTenant) + if err != nil { + return nil, err + } + + events = append(events, *verifiableEvent) + } + + // Sorting the events by MMR index guarantees that they're sorted in log append order. + sort.Slice(events, func(i, j int) bool { + return events[i].MMRIndex() < events[j].MMRIndex() + }) + + return events, nil +} + +// NewEventsV1AppEntry takes a single eventsv1 event JSON and returns a VerifiableEventsV1Event, +// providing just enough information to verify and identify the event. +func NewEventsV1AppEntry(eventJson []byte, logTenant string) (*app.AppEntry, error) { + + if logTenant == "" { + return nil, ErrInvalidEventsV1EventJson + } + + // special care is needed here to deal with uint64 types. json marshal / + // un marshal treats them as strings because they don't fit in a + // javascript Number + + // Unmarshal into a generic type to get just the bits we need. Use + // defered decoding to get the raw merklelog entry as it must be + // unmarshaled using protojson and the specific generated target type. + entry := struct { + Identity string `json:"identity,omitempty"` + OriginTenant string `json:"origin_tenant,omitempty"` + + Attributes map[string]any `json:"attributes,omitempty"` + Trails []string `json:"trails,omitempty"` + + // Note: the proof_details top level field can be ignored here because it is a 'oneof' + MerkleLogCommit json.RawMessage `json:"merklelog_commit,omitempty"` + }{} + + err := json.Unmarshal(eventJson, &entry) + if err != nil { + return nil, err + } + + // get the merklelog commit info + merkleLogCommit := &assets.MerkleLogCommit{} + err = protojson.Unmarshal(entry.MerkleLogCommit, merkleLogCommit) + if err != nil { + return nil, err + } + + // get the logID from the event log tenant + logUuid := strings.TrimPrefix(logTenant, "tenant/") + logId, err := uuid.Parse(logUuid) + if err != nil { + return nil, err + } + + // get the serialized bytes + serializableEvent := eventsv1.SerializableEvent{ + Attributes: entry.Attributes, + Trails: entry.Trails, + } + serializedBytes, err := serializableEvent.Serialize() + if err != nil { + return nil, err + } + + return app.NewAppEntry( + entry.Identity, + logId[:], + app.NewMMREntryFields( + byte(0), + serializedBytes, + ), + merkleLogCommit.Index, + ), nil +} diff --git a/app/eventsv1_test.go b/app/eventsv1_test.go new file mode 100644 index 0000000..ad83d33 --- /dev/null +++ b/app/eventsv1_test.go @@ -0,0 +1,95 @@ +package app + +import ( + "testing" + + "github.com/datatrails/go-datatrails-logverification/logverification/app" + "github.com/stretchr/testify/assert" +) + +func TestVerifiableEventsV1EventsFromData(t *testing.T) { + type args struct { + data []byte + logTenant string + } + tests := []struct { + name string + args args + expected []app.AppEntry + err error + }{ + { + name: "empty event list", + args: args{ + data: []byte(`{"events":[]}`), + logTenant: "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + }, + expected: []app.AppEntry{}, + err: ErrNoEvents, + }, + { + name: "list with invalid v1 event returns a validation error", + args: args{ + data: []byte(`{ + "events":[ + { + "merklelog_entry": { + "commit": { + "index": "0", + "idtimestamp": "018e3f48610b089800" + } + } + } + ] +}`), + logTenant: "", + }, + expected: nil, + err: ErrInvalidEventsV1EventJson, + }, + { + name: "single event list", + args: args{ + data: singleEventsv1EventJsonList, + logTenant: "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + }, + expected: []app.AppEntry{ + *app.NewAppEntry( + "events/01947000-3456-780f-bfa9-29881e3bac88", // app id + []byte{0x11, 0x27, 0x58, 0xce, 0xa8, 0xcb, 0x49, 0x24, 0x8d, 0xf8, 0xfc, 0xba, 0x1e, 0x31, 0xf8, 0xb0}, // log id + app.NewMMREntryFields( + byte(0), // domain + []byte{ + 0x34, 0x30, 0x3a, 0x7b, 0x22, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, + 0x3a, 0x7b, 0x22, 0x66, 0x6f, 0x6f, 0x22, 0x3a, + 0x22, 0x62, 0x61, 0x72, 0x22, 0x7d, 0x2c, 0x22, + 0x74, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x3a, + 0x5b, 0x5d, 0x7d, + }, // serialized bytes + ), + 1, // mmr index + ), + }, + err: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual, err := VerifiableEventsV1EventsFromData(test.args.data, test.args.logTenant) + + assert.Equal(t, test.err, err) + assert.Equal(t, len(test.expected), len(actual)) + + for index, expectedEvent := range test.expected { + actualEvent := actual[index] + + assert.Equal(t, expectedEvent.AppID(), actualEvent.AppID()) + assert.Equal(t, expectedEvent.LogID(), actualEvent.LogID()) + assert.Equal(t, expectedEvent.MMRIndex(), actualEvent.MMRIndex()) + + assert.Equal(t, expectedEvent.SerializedBytes(), actualEvent.SerializedBytes()) + } + }) + } +} diff --git a/app/readappdata.go b/app/readappdata.go new file mode 100644 index 0000000..4fe96fc --- /dev/null +++ b/app/readappdata.go @@ -0,0 +1,45 @@ +package app + +import ( + "bufio" + "os" + "path/filepath" +) + +func stdinToAppData() ([]byte, error) { + return scannerToAppData(bufio.NewScanner(os.Stdin)) +} + +func filePathToAppData(filePath string) ([]byte, error) { + filePath, err := filepath.Abs(filePath) + if err != nil { + return nil, err + } + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + return scannerToAppData(bufio.NewScanner(f)) +} + +func scannerToAppData(scanner *bufio.Scanner) ([]byte, error) { + var data []byte + for scanner.Scan() { + data = append(data, scanner.Bytes()...) + } + if err := scanner.Err(); err != nil { + return nil, err + } + return data, nil +} + +// ReadAppData reads the app data from stdin or from a given file path +func ReadAppData(fromStdIn bool, filePath string) ([]byte, error) { + + if fromStdIn { + return stdinToAppData() + } + + return filePathToAppData(filePath) + +} diff --git a/cmdctx.go b/cmdctx.go index c846077..0fd7b10 100644 --- a/cmdctx.go +++ b/cmdctx.go @@ -8,6 +8,13 @@ import ( "github.com/datatrails/go-datatrails-merklelog/massifs" ) +// MassifGetter gets a specific massif based on the massifIndex given for a tenant log +type MassifGetter interface { + GetMassif( + ctx context.Context, tenantIdentity string, massifIndex uint64, opts ...massifs.ReaderOption, + ) (massifs.MassifContext, error) +} + type MassifReader interface { GetVerifiedContext( ctx context.Context, tenantIdentity string, massifIndex uint64, @@ -23,9 +30,7 @@ type MassifReader interface { GetLazyContext( ctx context.Context, tenantIdentity string, which massifs.LogicalBlob, opts ...massifs.ReaderOption, ) (massifs.LogBlobContext, uint64, error) - GetMassif( - ctx context.Context, tenantIdentity string, massifIndex uint64, opts ...massifs.ReaderOption, - ) (massifs.MassifContext, error) + MassifGetter } // CmdCtx holds shared config and config derived state for all commands diff --git a/consts_test.go b/consts_test.go new file mode 100644 index 0000000..1af3871 --- /dev/null +++ b/consts_test.go @@ -0,0 +1,148 @@ +package veracity + +/** + * file for all the test constants + */ + +var ( + eventsV1Event = []byte(` +{ + "identity": "events/01947000-3456-780f-bfa9-29881e3bac88", + "attributes": { + "foo": "bar" + }, + "trails": [], + "origin_tenant": "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + "created_by": "2ef471c2-f997-4503-94c8-60b5c929a3c3", + "created_at": 1737045849174, + "confirmation_status": "CONFIRMED", + "merklelog_commit": { + "index": "1", + "idtimestamp": "019470003611017900" + } +}`) + + eventsV1SingleEventList = []byte(` +{ + "events": [ + { + "identity": "events/01947000-3456-780f-bfa9-29881e3bac88", + "attributes": { + "foo": "bar" + }, + "trails": [], + "origin_tenant": "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + "created_by": "2ef471c2-f997-4503-94c8-60b5c929a3c3", + "created_at": 1737045849174, + "confirmation_status": "CONFIRMED", + "merklelog_commit": { + "index": "1", + "idtimestamp": "019470003611017900" + } + } + ] +} + `) + + assetsV2Event = []byte(` +{ + "identity": "assets/899e00a2-29bc-4316-bf70-121ce2044472/events/450dce94-065e-4f6a-bf69-7b59f28716b6", + "asset_identity": "assets/899e00a2-29bc-4316-bf70-121ce2044472", + "event_attributes": {}, + "asset_attributes": { + "arc_display_name": "Default asset", + "default": "true", + "arc_description": "Collection for Events not specifically associated with any specific Asset" + }, + "operation": "NewAsset", + "behaviour": "AssetCreator", + "timestamp_declared": "2025-01-16T16:12:38Z", + "timestamp_accepted": "2025-01-16T16:12:38Z", + "timestamp_committed": "2025-01-16T16:12:38.576970217Z", + "principal_declared": { + "issuer": "https://accounts.google.com", + "subject": "105632894023856861149", + "display_name": "Henry SocialTest", + "email": "henry.socialtest@gmail.com" + }, + "principal_accepted": { + "issuer": "https://accounts.google.com", + "subject": "105632894023856861149", + "display_name": "Henry SocialTest", + "email": "henry.socialtest@gmail.com" + }, + "confirmation_status": "CONFIRMED", + "transaction_id": "", + "block_number": 0, + "transaction_index": 0, + "from": "0x412bB2Ecd6f2bDf26D64de834Fa17167192F4c0d", + "tenant_identity": "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + "merklelog_entry": { + "commit": { + "index": "0", + "idtimestamp": "01946fe35fc6017900" + }, + "confirm": { + "mmr_size": "1", + "root": "YecBKn8UtUZ6hlTnrnXIlKvNOZKuMCIemNdNA8wOyjk=", + "timestamp": "1737043961154", + "idtimestamp": "", + "signed_tree_head": "" + }, + "unequivocal": null + } +}`) + + assetsV2SingleEventList = []byte(` +{ + "events": [ + { + "identity": "assets/899e00a2-29bc-4316-bf70-121ce2044472/events/450dce94-065e-4f6a-bf69-7b59f28716b6", + "asset_identity": "assets/899e00a2-29bc-4316-bf70-121ce2044472", + "event_attributes": {}, + "asset_attributes": { + "arc_display_name": "Default asset", + "default": "true", + "arc_description": "Collection for Events not specifically associated with any specific Asset" + }, + "operation": "NewAsset", + "behaviour": "AssetCreator", + "timestamp_declared": "2025-01-16T16:12:38Z", + "timestamp_accepted": "2025-01-16T16:12:38Z", + "timestamp_committed": "2025-01-16T16:12:38.576970217Z", + "principal_declared": { + "issuer": "https://accounts.google.com", + "subject": "105632894023856861149", + "display_name": "Henry SocialTest", + "email": "henry.socialtest@gmail.com" + }, + "principal_accepted": { + "issuer": "https://accounts.google.com", + "subject": "105632894023856861149", + "display_name": "Henry SocialTest", + "email": "henry.socialtest@gmail.com" + }, + "confirmation_status": "CONFIRMED", + "transaction_id": "", + "block_number": 0, + "transaction_index": 0, + "from": "0x412bB2Ecd6f2bDf26D64de834Fa17167192F4c0d", + "tenant_identity": "tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0", + "merklelog_entry": { + "commit": { + "index": "0", + "idtimestamp": "01946fe35fc6017900" + }, + "confirm": { + "mmr_size": "1", + "root": "YecBKn8UtUZ6hlTnrnXIlKvNOZKuMCIemNdNA8wOyjk=", + "timestamp": "1737043961154", + "idtimestamp": "", + "signed_tree_head": "" + }, + "unequivocal": null + } + } + ] +}`) +) diff --git a/ediag.go b/ediag.go index feba87f..3c7fb19 100644 --- a/ediag.go +++ b/ediag.go @@ -6,12 +6,14 @@ import ( "encoding/binary" "fmt" "reflect" + "strings" "time" "github.com/datatrails/go-datatrails-merklelog/massifs" "github.com/datatrails/go-datatrails-merklelog/massifs/snowflakeid" "github.com/datatrails/go-datatrails-merklelog/mmr" "github.com/datatrails/go-datatrails-simplehash/simplehash" + veracityapp "github.com/datatrails/veracity/app" "github.com/urfave/cli/v2" ) @@ -29,7 +31,15 @@ func NewEventDiagCmd() *cli.Command { }, }, Action: func(cCtx *cli.Context) error { - decodedEvents, err := stdinToDecodedEvents() + + tenantIdentity := cCtx.String("tenant") + + appData, err := veracityapp.ReadAppData(cCtx.Args().Len() == 0, cCtx.Args().Get(0)) + if err != nil { + return err + } + + appEntries, err := veracityapp.AppDataToVerifiableLogEntries(appData, tenantIdentity) if err != nil { return err } @@ -47,15 +57,15 @@ func NewEventDiagCmd() *cli.Command { return false } - for _, decodedEvent := range decodedEvents { + for _, appEntry := range appEntries { - if decodedEvent.MerkleLog == nil || decodedEvent.MerkleLog.Commit == nil { + if appEntry.MMRIndex() == 0 { continue } // Get the mmrIndex from the request and then compute the massif // it implies based on the massifHeight command line option. - mmrIndex := decodedEvent.MerkleLog.Commit.Index + mmrIndex := appEntry.MMRIndex() massifIndex := massifs.MassifIndexFromMMRIndex(cmd.massifHeight, mmrIndex) tenantIdentity := cCtx.String("tenant") if tenantIdentity == "" { @@ -64,7 +74,10 @@ func NewEventDiagCmd() *cli.Command { // assets, this is true regardless of which tenancy the // record is fetched from. Those same events will appear in // the logs of all tenants they were shared with. - tenantIdentity = decodedEvent.V3Event.TenantIdentity + tenantIdentity, err = appEntry.LogTenant() + if err != nil { + return err + } } // read the massif blob cmd.massif, err = cmd.massifReader.GetMassif(context.Background(), tenantIdentity, massifIndex) @@ -73,8 +86,16 @@ func NewEventDiagCmd() *cli.Command { } // Get the human time from the idtimestamp committed on the event. + idTimestamp, err := appEntry.IDTimestamp(&cmd.massif) + if err != nil { + return err + } - eventIDTimestamp, _, err := massifs.SplitIDTimestampHex(decodedEvent.MerkleLog.Commit.Idtimestamp) + idTimestampWithEpoch := make([]byte, len(idTimestamp)+1) + idTimestampWithEpoch[0] = byte(0) // 0 epoch + copy(idTimestampWithEpoch[1:], idTimestamp) + + eventIDTimestamp, _, err := massifs.SplitIDTimestampBytes(idTimestampWithEpoch) if err != nil { return err } @@ -85,7 +106,7 @@ func NewEventDiagCmd() *cli.Command { leafIndex := mmr.LeafIndex(mmrIndex) // Note that the banner info is all from the event response - fmt.Printf("%d %s %s\n", leafIndex, time.UnixMilli(eventIDTimestampMS).Format(time.RFC3339Nano), decodedEvent.V3Event.Identity) + fmt.Printf("%d %s %s\n", leafIndex, time.UnixMilli(eventIDTimestampMS).Format(time.RFC3339Nano), appEntry.AppID()) leafIndexMassif, err := cmd.massif.GetMassifLeafIndex(leafIndex) if err != nil { @@ -100,6 +121,8 @@ func NewEventDiagCmd() *cli.Command { return err } + logTrieKey := massifs.GetTrieKey(cmd.massif.Data, cmd.massif.IndexStart(), leafIndexMassif) + logTrieIDTimestampBytes := logTrieEntry[massifs.TrieEntryIdTimestampStart:massifs.TrieEntryIdTimestampEnd] logTrieIDTimestamp := binary.BigEndian.Uint64(logTrieIDTimestampBytes) unixMS, err := snowflakeid.IDUnixMilli(logTrieIDTimestamp, uint8(cmd.massif.Start.CommitmentEpoch)) @@ -108,31 +131,37 @@ func NewEventDiagCmd() *cli.Command { } idTime := time.UnixMilli(unixMS) + // TODO: log version 1 uses the uuid bytes of the log tenant + // so we need to handle that here as well as log version 0. trieKey := massifs.NewTrieKey( massifs.KeyTypeApplicationContent, []byte(tenantIdentity), - []byte(decodedEvent.V3Event.Identity)) + []byte(strings.TrimPrefix(appEntry.AppID(), "public"))) if len(trieKey) != massifs.TrieKeyBytes { return massifs.ErrIndexEntryBadSize } cmpPrint( " |%x trie-key\n", - " |%x != log-trie-key %x\n", trieKey[:32], logTrieEntry[:32]) + " |%x != log-trie-key %x\n", trieKey[:32], logTrieKey[:32]) fmt.Printf(" |%x %s log-idtimestamp\n", logTrieIDTimestampBytes, idTime.Format(time.DateTime)) cmpPrint( " |%x idtimestamp\n", " |%x != log-idtimestamp %x\n", eventIDTimestamp, logTrieIDTimestamp) // Compute the event data hash, independent of domain and idtimestamp + v3Event, err := simplehash.V3FromEventJSON(appEntry.SerializedBytes()) // NOTE for assetsv2 the serialized bytes is actually the event json API response + if err != nil { + return err + } eventHasher := sha256.New() - if err = simplehash.V3HashEvent(eventHasher, decodedEvent.V3Event); err != nil { + if err = simplehash.V3HashEvent(eventHasher, v3Event); err != nil { return err } eventHash := eventHasher.Sum(nil) fmt.Printf(" |%x v3hash (just the schema fields hashed)\n", eventHash) if cCtx.Bool("bendump") { - bencode, err2 := bencodeEvent(decodedEvent.V3Event) + bencode, err2 := bencodeEvent(v3Event) if err2 != nil { return err2 } @@ -141,7 +170,7 @@ func NewEventDiagCmd() *cli.Command { leafHasher := simplehash.NewHasherV3() err = leafHasher.HashEventFromV3( - decodedEvent.V3Event, + v3Event, simplehash.WithPrefix([]byte{LeafTypePlain}), simplehash.WithIDCommitted(eventIDTimestamp)) if err != nil { diff --git a/event.json b/event.json index 4a8cb98..83e8a09 100644 --- a/event.json +++ b/event.json @@ -1 +1 @@ -{"identity": "publicassets/87dd2e5a-42b4-49a5-8693-97f40a5af7f8/events/a022f458-8e55-4d63-a200-4172a42fc2aa", "asset_identity": "publicassets/87dd2e5a-42b4-49a5-8693-97f40a5af7f8", "event_attributes": {"arc_access_policy_always_read": [ {"wallet" :"0x0E29670b420B7f2E8E699647b632cdE49D868dA7","tessera" :"SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ="}], "arc_access_policy_asset_attributes_read": [ {"attribute" :"*","0x4609ea6bbe85F61bc64760273ce6D89A632B569f" :"wallet","SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ=" :"tessera"}], "arc_access_policy_event_arc_display_type_read": [ {"value" :"*","0x4609ea6bbe85F61bc64760273ce6D89A632B569f" :"wallet","SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ=" :"tessera"}]}, "asset_attributes": {"arc_display_type": "public-test", "arc_display_name": "Dava Derby"}, "operation": "NewAsset", "behaviour": "AssetCreator", "timestamp_declared": "2024-05-24T07:26:58Z", "timestamp_accepted": "2024-05-24T07:26:58Z", "timestamp_committed": "2024-05-24T07:27:00.200Z", "principal_declared": {"issuer":"", "subject":"", "display_name":"", "email":""}, "principal_accepted": {"issuer":"", "subject":"", "display_name":"", "email":""}, "confirmation_status": "CONFIRMED", "transaction_id": "0xc891533b1806555fff9ab853cd9ce1bb2c00753609070a875a44ec53a6c1213b", "block_number": 7932, "transaction_index": 1, "from": "0x0E29670b420B7f2E8E699647b632cdE49D868dA7", "tenant_identity": "tenant/7dfaa5ef-226f-4f40-90a5-c015e59998a8", "merklelog_entry": {"commit":{"index":"663", "idtimestamp":"018fa97ef269039b00"}, "confirm":{"mmr_size":"664", "root":"/rlMNJhlay9CUuO3LgX4lSSDK6dDhtKesCO50CtrHr4=", "timestamp":"1716535620409", "idtimestamp":"", "signed_tree_head":""}, "unequivocal":null}} +{"identity": "publicassets/87dd2e5a-42b4-49a5-8693-97f40a5af7f8/events/a022f458-8e55-4d63-a200-4172a42fc2aa", "asset_identity": "publicassets/87dd2e5a-42b4-49a5-8693-97f40a5af7f8", "event_attributes": {"arc_access_policy_always_read": [ {"wallet" :"0x0E29670b420B7f2E8E699647b632cdE49D868dA7","tessera" :"SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ="}], "arc_access_policy_asset_attributes_read": [ {"attribute" :"*","0x4609ea6bbe85F61bc64760273ce6D89A632B569f" :"wallet","SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ=" :"tessera"}], "arc_access_policy_event_arc_display_type_read": [ {"value" :"*","0x4609ea6bbe85F61bc64760273ce6D89A632B569f" :"wallet","SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ=" :"tessera"}]}, "asset_attributes": {"arc_display_type": "public-test", "arc_display_name": "Dava Derby"}, "operation": "NewAsset", "behaviour": "AssetCreator", "timestamp_declared": "2024-05-24T07:26:58Z", "timestamp_accepted": "2024-05-24T07:26:58Z", "timestamp_committed": "2024-05-24T07:27:00.200Z", "principal_declared": {"issuer":"", "subject":"", "display_name":"", "email":""}, "principal_accepted": {"issuer":"", "subject":"", "display_name":"", "email":""}, "confirmation_status": "CONFIRMED", "transaction_id": "0xc891533b1806555fff9ab853cd9ce1bb2c00753609070a875a44ec53a6c1213b", "block_number": 7932, "transaction_index": 1, "from": "0x0E29670b420B7f2E8E699647b632cdE49D868dA7", "tenant_identity": "tenant/7dfaa5ef-226f-4f40-90a5-c015e59998a8", "merklelog_entry": {"commit":{"index":"663", "idtimestamp":"018fa97ef269039b00"}, "confirm":{"mmr_size":"664", "root":"/rlMNJhlay9CUuO3LgX4lSSDK6dDhtKesCO50CtrHr4=", "timestamp":"1716535620409", "idtimestamp":"", "signed_tree_head":""}, "unequivocal":null}} \ No newline at end of file diff --git a/go.mod b/go.mod index 6b34ea2..ad6f634 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,24 @@ module github.com/datatrails/veracity -go 1.22 +go 1.23.0 require ( - github.com/datatrails/go-datatrails-common v0.18.0 - github.com/datatrails/go-datatrails-common-api-gen v0.4.6 - github.com/datatrails/go-datatrails-logverification v0.2.0 - github.com/datatrails/go-datatrails-merklelog/massifs v0.3.0 + github.com/datatrails/go-datatrails-common v0.18.3 + github.com/datatrails/go-datatrails-common-api-gen v0.6.1 + github.com/datatrails/go-datatrails-logverification v0.4.0 + github.com/datatrails/go-datatrails-merklelog/massifs v0.3.1 github.com/datatrails/go-datatrails-merklelog/mmr v0.1.1 github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.1.0 + github.com/datatrails/go-datatrails-serialization/eventsv1 v0.0.2 github.com/datatrails/go-datatrails-simplehash v0.0.5 github.com/gosuri/uiprogress v0.0.1 github.com/urfave/cli/v2 v2.27.1 github.com/zeebo/bencode v1.0.0 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 ) require ( - github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/gosuri/uilive v0.0.4 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -25,8 +26,8 @@ require ( require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 github.com/Azure/go-amqp v1.0.5 // indirect @@ -41,9 +42,9 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.6.0 @@ -55,21 +56,21 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/openzipkin-contrib/zipkin-go-opentracing v0.5.0 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.2 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8bad00e..909edc3 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.1 h1:Xy/qV1DyOhhqsU/z0PyFMJfYCxnzna+vBEUtFW0ksQo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.1/go.mod h1:oib6iWdC+sILvNUoJbbBn3xv7TXow7mEp/WRcsYvmow= github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 h1:o/Ws6bEqMeKZUfj1RRm3mQ51O8JGU5w+Qdg2AhHib6A= github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1/go.mod h1:6QAMYBAbQeeKX+REFJMZ1nFWu9XLw/PPcjYpuc9RDFs= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 h1:QSdcrd/UFJv6Bp/CfoVf2SrENpFn9P6Yh8yb+xNhYMM= @@ -42,31 +42,34 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/datatrails/go-datatrails-common v0.18.0 h1:OeNP4EdIjhLHnE/mdN2/kp6Fq+xOnE6Y2p3DKg4xXHw= -github.com/datatrails/go-datatrails-common v0.18.0/go.mod h1:fBDqKHRLUYcictdWdLrIhKNhieKVE2r0II8vyETCuhM= -github.com/datatrails/go-datatrails-common-api-gen v0.4.6 h1:yzviWC2jBOC3ItotQQlWKmMvqpFJXOkuBdU0ml84Fjg= -github.com/datatrails/go-datatrails-common-api-gen v0.4.6/go.mod h1:OQN91xvlW6xcWTFvwsM2Nn4PZwFAIOE52FG7yRl4QPQ= -github.com/datatrails/go-datatrails-logverification v0.2.0 h1:CzCSGw1Sn1KUd/X32atSk9PTQpl8QBS5BXn+hPwpwmI= -github.com/datatrails/go-datatrails-logverification v0.2.0/go.mod h1:hu+VUZOvkPIHlhp1+qTELNozgsdvlENbXDzt/6Kcoc8= -github.com/datatrails/go-datatrails-merklelog/massifs v0.3.0 h1:3rWo1nm/90+EnbM1xJFinRAKFnDl1mS3JmEALTsqAak= -github.com/datatrails/go-datatrails-merklelog/massifs v0.3.0/go.mod h1:3V08x15NPbzBTSrvjvgzUA0ADkxBRV7m3p5ODElmB2A= +github.com/datatrails/go-datatrails-common v0.18.3 h1:9h/8skTl1yCV/SeNRwYLETPZjikjYgKxJX65wXy12BM= +github.com/datatrails/go-datatrails-common v0.18.3/go.mod h1:YUUAwrD7SQFXverHUxt2subZxTfSp76zUHxtmijNlvM= +github.com/datatrails/go-datatrails-common-api-gen v0.6.1 h1:rkzx2FBdTTNirLNLWHpXRme3GrOssPP27reQUwdFAJc= +github.com/datatrails/go-datatrails-common-api-gen v0.6.1/go.mod h1:rTMGdMdu5M6mGpbXZy1D84cBTGE8JwsDH6BYh9LJlmA= +github.com/datatrails/go-datatrails-logverification v0.4.0 h1:oKjZPdGAkn4xBfmteLxPKiWc5BHXzec0m9KKmYbnMpA= +github.com/datatrails/go-datatrails-logverification v0.4.0/go.mod h1:1KUFqomMuwZY9HcyTNkyTYAbAqYJQcZ6QZiv+5nGqdI= +github.com/datatrails/go-datatrails-merklelog/massifs v0.3.1 h1:RR1FVJ85iCrZIoMzcfxPLZsPCYl7XvlZ4S8sm2TxFi8= +github.com/datatrails/go-datatrails-merklelog/massifs v0.3.1/go.mod h1:3V08x15NPbzBTSrvjvgzUA0ADkxBRV7m3p5ODElmB2A= github.com/datatrails/go-datatrails-merklelog/mmr v0.1.1 h1:Ro2fYdDYxGGcPmudYuvPonx78GkdQuKwzrdknLR55cE= github.com/datatrails/go-datatrails-merklelog/mmr v0.1.1/go.mod h1:B/Kkz4joZTiTz0q/9FFAgHR+Tcn6UxtphMuCzamAc9Q= github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.1.0 h1:q9RXtAGydXKSJjARnFObNu743cbfIOfERTXiiVa2tF4= github.com/datatrails/go-datatrails-merklelog/mmrtesting v0.1.0/go.mod h1:rWFjeK1NU7qnhl9+iKdjASpw/CkPwDAOPHsERYR7uEQ= +github.com/datatrails/go-datatrails-serialization/eventsv1 v0.0.2 h1:yEk+0KvWkn/xYbf3WgdFCAZNkLE4pze9ySqeCr6Pnos= +github.com/datatrails/go-datatrails-serialization/eventsv1 v0.0.2/go.mod h1:9i6Tip2lIXwSZ3SxP7XEhU2eQ9zkpxhEBmPmlOGqv/8= github.com/datatrails/go-datatrails-simplehash v0.0.5 h1:igu4QRYO87RQXrJlqSm3fgMA2Q0F4jglWqBlfvKrXKQ= github.com/datatrails/go-datatrails-simplehash v0.0.5/go.mod h1:XuOwViwdL+dyz7fGYIjaByS1ElMFsrVI0goKX0bNimA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= @@ -117,8 +120,9 @@ github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7s github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -132,8 +136,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= @@ -156,10 +160,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -168,8 +172,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -183,8 +187,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -197,21 +201,21 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/localwriter.go b/localwriter.go index b0bbd07..9d63494 100644 --- a/localwriter.go +++ b/localwriter.go @@ -8,6 +8,10 @@ import ( "github.com/datatrails/go-datatrails-merklelog/massifs" ) +const ( + readWriteAllPermission = 0666 +) + // FileWriteAppendOpener is an interface for opening a file for writing // The Open implementation must open for *append*, and must create the file if it does not exist. // The Create implementation must truncate the file if it exists, and create it if it does not. @@ -19,7 +23,7 @@ func (*FileWriteAppendOpener) Open(name string) (io.WriteCloser, error) { if err != nil { return nil, err } - return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, readWriteAllPermission) } // Create ensures the named file exists, is empty and is writable diff --git a/massifs.go b/massifs.go index bf75342..dac4824 100644 --- a/massifs.go +++ b/massifs.go @@ -129,7 +129,7 @@ to produce the desired information computationaly produce // massifs all have twice the amount of space reserved for // the trie than we need. trieDataSize := massifs.TrieEntryBytes * (1 << height) - peakStackSize := massifs.PeakStackLen(mi) * 32 + peakStackSize := massifs.PeakStackLen(mi) * massifs.LogEntryBytes switch cCtx.String("format") { case tableFmtName: row = fmt.Sprintf(tableSizesFmt, trieDataSize, peakStackSize) + row diff --git a/nodescan.go b/nodescan.go index cfb7799..8fa95ca 100644 --- a/nodescan.go +++ b/nodescan.go @@ -37,9 +37,9 @@ func NewNodeScanCmd() *cli.Command { } start := cmd.massif.LogStart() count := cmd.massif.Count() - for i := uint64(0); i < count; i++ { + for i := range count { entry := cmd.massif.Data[start+i*massifs.ValueBytes : start+i*massifs.ValueBytes+massifs.ValueBytes] - if bytes.Compare(entry, targetValue) == 0 { + if bytes.Equal(entry, targetValue) { fmt.Printf("%d\n", i+cmd.massif.Start.FirstIndex) return nil } diff --git a/readeventsfile.go b/readeventsfile.go deleted file mode 100644 index a0d6869..0000000 --- a/readeventsfile.go +++ /dev/null @@ -1,155 +0,0 @@ -package veracity - -import ( - "bufio" - "bytes" - "encoding/json" - "errors" - "os" - "path/filepath" - - "github.com/datatrails/go-datatrails-logverification/logverification" -) - -var ( - ErrInvalidV3Event = errors.New(`json is not in expected v3event format`) -) - -func stdinToVerifiableEvents() ([]logverification.VerifiableEvent, error) { - return scannerToVerifiableEvents(bufio.NewScanner(os.Stdin)) -} - -func filePathToVerifiableEvents(filePath string) ([]logverification.VerifiableEvent, error) { - filePath, err := filepath.Abs(filePath) - if err != nil { - return nil, err - } - f, err := os.Open(filePath) - if err != nil { - return nil, err - } - return scannerToVerifiableEvents(bufio.NewScanner(f)) -} - -func stdinToDecodedEvents() ([]logverification.DecodedEvent, error) { - return scannerToDecodedEvents(bufio.NewScanner(os.Stdin)) -} - -func scannerToDecodedEvents(scanner *bufio.Scanner) ([]logverification.DecodedEvent, error) { - var data []byte - for scanner.Scan() { - data = append(data, scanner.Bytes()...) - } - if err := scanner.Err(); err != nil { - return nil, err - } - return DecodedEventsFromData(data) -} - -func scannerToVerifiableEvents(scanner *bufio.Scanner) ([]logverification.VerifiableEvent, error) { - var data []byte - for scanner.Scan() { - data = append(data, scanner.Bytes()...) - } - if err := scanner.Err(); err != nil { - return nil, err - } - return VerifiableEventsFromData(data) -} - -func VerifiableEventsFromData(data []byte) ([]logverification.VerifiableEvent, error) { - - // Accept either the list events response format or a single event. Peak - // into the json data to pick which. - eventsJson, err := eventListFromData(data) - if err != nil { - return nil, err - } - - verifiableEvents, err := logverification.NewVerifiableEvents(eventsJson) - if err != nil { - return nil, err - } - - for _, event := range verifiableEvents { - validationErr := event.Validate() - if validationErr != nil { - return nil, validationErr - } - } - - return verifiableEvents, nil -} - -func DecodedEventsFromData(data []byte) ([]logverification.DecodedEvent, error) { - - // Accept either the list events response format or a single event. Peak - // into the json data to pick which. - eventsJson, err := eventListFromData(data) - if err != nil { - return nil, err - } - - decodedEvents, err := logverification.NewDecodedEvents(eventsJson) - if err != nil { - return nil, err - } - - for _, event := range decodedEvents { - validationErr := event.Validate() - if validationErr != nil { - return nil, validationErr - } - } - - return decodedEvents, nil -} - -// eventListFromData normalises a json encoded event or *list* of events, by -// always returning a list of json encoded events. -// -// 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 eventListFromData(data []byte) ([]byte, error) { - var err error - - doc := struct { - Events []json.RawMessage `json:"events,omitempty"` - NextPageToken json.RawMessage `json:"next_page_token,omitempty"` - }{} - - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - 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 err == nil { - return data, nil - } - - // 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 -} diff --git a/readeventsfile_test.go b/readeventsfile_test.go deleted file mode 100644 index 8c4fff7..0000000 --- a/readeventsfile_test.go +++ /dev/null @@ -1,540 +0,0 @@ -package veracity - -import ( - "testing" - - "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" - "github.com/datatrails/go-datatrails-logverification/logverification" - "github.com/datatrails/go-datatrails-simplehash/simplehash" - "github.com/stretchr/testify/assert" -) - -func TestEventListFromData(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 := eventListFromData(test.args.data) - - assert.Equal(t, test.wantErr, err != nil) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestDecodedEventsFromData(t *testing.T) { - type args struct { - data []byte - } - tests := []struct { - name string - args args - expected []logverification.DecodedEvent - err error - }{ - { - name: "empty event list", - args: args{ - data: []byte(`{"events":[]}`), - }, - expected: []logverification.DecodedEvent{}, - err: nil, - }, - { - name: "list with invalid v3 event returns a validation error", - args: args{ - data: []byte(`{ - "events":[ - { - "merklelog_entry": { - "commit": { - "index": "0", - "idtimestamp": "018e3f48610b089800" - } - } - } - ] -}`), - }, - expected: nil, - err: logverification.ErrNonEmptyEventIDRequired, - }, - { - name: "single event list", - args: args{ - data: []byte(`{ - "events":[ - { - "identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - "asset_identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8", - "event_attributes": {}, - "asset_attributes": { - "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee" - }, - "operation": "NewAsset", - "behaviour": "AssetCreator", - "timestamp_declared": "2024-03-14T23:24:50Z", - "timestamp_accepted": "2024-03-14T23:24:50Z", - "timestamp_committed": "2024-03-22T11:13:55.557Z", - "principal_declared": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "principal_accepted": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "confirmation_status": "CONFIRMED", - "transaction_id": "", - "block_number": 0, - "transaction_index": 0, - "from": "0xF17B3B9a3691846CA0533Ce01Fa3E35d6d6f714C", - "tenant_identity": "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - "merklelog_entry": { - "commit": { - "index": "0", - "idtimestamp": "018e3f48610b089800" - }, - "confirm": { - "mmr_size": "7", - "root": "XdcejozGdFYn7JTa/5PUodWtmomUuGuTTouMvxyDevo=", - "timestamp": "1711106035557", - "idtimestamp": "", - "signed_tree_head": "" - }, - "unequivocal": null - } - } - ] -}`), - }, - expected: []logverification.DecodedEvent{ - { - V3Event: simplehash.V3Event{ - Identity: "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - EventAttributes: map[string]any{}, - AssetAttributes: map[string]any{ - "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee", - }, - Operation: "NewAsset", - Behaviour: "AssetCreator", - TimestampDeclared: "2024-03-14T23:24:50Z", - TimestampAccepted: "2024-03-14T23:24:50Z", - TimestampCommitted: "2024-03-22T11:13:55.557Z", - PrincipalDeclared: map[string]any{ - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "", - }, - PrincipalAccepted: map[string]any{ - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "", - }, - TenantIdentity: "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - }, - MerkleLog: &assets.MerkleLogEntry{ - Commit: &assets.MerkleLogCommit{ - Index: 0, - Idtimestamp: "018e3f48610b089800", - }, - Confirm: &assets.MerkleLogConfirm{ - MmrSize: 7, - Root: []byte{93, 215, 30, 142, 140, 198, 116, 86, 39, 236, 148, 218, 255, 147, 212, 161, 213, 173, 154, 137, 148, 184, 107, 147, 78, 139, 140, 191, 28, 131, 122, 250}, - Timestamp: 1711106035557, - Idtimestamp: "", - SignedTreeHead: nil, - }, - }, - }, - }, - err: nil, - }, - { - name: "single event", - args: args{ - data: []byte(`{ - "identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - "asset_identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8", - "event_attributes": {}, - "asset_attributes": { - "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee" - }, - "operation": "NewAsset", - "behaviour": "AssetCreator", - "timestamp_declared": "2024-03-14T23:24:50Z", - "timestamp_accepted": "2024-03-14T23:24:50Z", - "timestamp_committed": "2024-03-22T11:13:55.557Z", - "principal_declared": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "principal_accepted": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "confirmation_status": "CONFIRMED", - "transaction_id": "", - "block_number": 0, - "transaction_index": 0, - "from": "0xF17B3B9a3691846CA0533Ce01Fa3E35d6d6f714C", - "tenant_identity": "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - "merklelog_entry": { - "commit": { - "index": "0", - "idtimestamp": "018e3f48610b089800" - }, - "confirm": { - "mmr_size": "7", - "root": "XdcejozGdFYn7JTa/5PUodWtmomUuGuTTouMvxyDevo=", - "timestamp": "1711106035557", - "idtimestamp": "", - "signed_tree_head": "" - }, - "unequivocal": null - } -}`), - }, - expected: []logverification.DecodedEvent{ - { - V3Event: simplehash.V3Event{ - Identity: "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - EventAttributes: map[string]any{}, - AssetAttributes: map[string]any{ - "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee", - }, - Operation: "NewAsset", - Behaviour: "AssetCreator", - TimestampDeclared: "2024-03-14T23:24:50Z", - TimestampAccepted: "2024-03-14T23:24:50Z", - TimestampCommitted: "2024-03-22T11:13:55.557Z", - PrincipalDeclared: map[string]any{ - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "", - }, - PrincipalAccepted: map[string]any{ - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "", - }, - TenantIdentity: "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - }, - MerkleLog: &assets.MerkleLogEntry{ - Commit: &assets.MerkleLogCommit{ - Index: 0, - Idtimestamp: "018e3f48610b089800", - }, - Confirm: &assets.MerkleLogConfirm{ - MmrSize: 7, - Root: []byte{93, 215, 30, 142, 140, 198, 116, 86, 39, 236, 148, 218, 255, 147, 212, 161, 213, 173, 154, 137, 148, 184, 107, 147, 78, 139, 140, 191, 28, 131, 122, 250}, - Timestamp: 1711106035557, - Idtimestamp: "", - SignedTreeHead: nil, - }, - }, - }, - }, - err: nil, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual, err := DecodedEventsFromData(test.args.data) - - assert.Equal(t, test.err, err) - assert.Equal(t, len(test.expected), len(actual)) - - for index, expectedEvent := range test.expected { - actualEvent := actual[index] - - assert.Equal(t, expectedEvent.V3Event, actualEvent.V3Event) - - assert.Equal(t, expectedEvent.MerkleLog.Commit.Index, actualEvent.MerkleLog.Commit.Index) - assert.Equal(t, expectedEvent.MerkleLog.Commit.Idtimestamp, actualEvent.MerkleLog.Commit.Idtimestamp) - - assert.Equal(t, expectedEvent.MerkleLog.Confirm.MmrSize, actualEvent.MerkleLog.Confirm.MmrSize) - assert.Equal(t, expectedEvent.MerkleLog.Confirm.Root, actualEvent.MerkleLog.Confirm.Root) - assert.Equal(t, expectedEvent.MerkleLog.Confirm.Timestamp, actualEvent.MerkleLog.Confirm.Timestamp) - } - }) - } -} - -func TestVerifiableEventsFromData(t *testing.T) { - type args struct { - data []byte - } - tests := []struct { - name string - args args - expected []logverification.VerifiableEvent - err error - }{ - { - name: "empty event list", - args: args{ - data: []byte(`{"events":[]}`), - }, - expected: []logverification.VerifiableEvent{}, - err: nil, - }, - { - name: "list with invalid v3 event returns a validation error", - args: args{ - data: []byte(`{ - "events":[ - { - "merklelog_entry": { - "commit": { - "index": "0", - "idtimestamp": "018e3f48610b089800" - } - } - } - ] -}`), - }, - expected: nil, - err: logverification.ErrNonEmptyEventIDRequired, - }, - { - name: "single event list", - args: args{ - data: []byte(`{ - "events":[ - { - "identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - "asset_identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8", - "event_attributes": {}, - "asset_attributes": { - "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee" - }, - "operation": "NewAsset", - "behaviour": "AssetCreator", - "timestamp_declared": "2024-03-14T23:24:50Z", - "timestamp_accepted": "2024-03-14T23:24:50Z", - "timestamp_committed": "2024-03-22T11:13:55.557Z", - "principal_declared": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "principal_accepted": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "confirmation_status": "CONFIRMED", - "transaction_id": "", - "block_number": 0, - "transaction_index": 0, - "from": "0xF17B3B9a3691846CA0533Ce01Fa3E35d6d6f714C", - "tenant_identity": "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - "merklelog_entry": { - "commit": { - "index": "0", - "idtimestamp": "018e3f48610b089800" - }, - "confirm": { - "mmr_size": "7", - "root": "XdcejozGdFYn7JTa/5PUodWtmomUuGuTTouMvxyDevo=", - "timestamp": "1711106035557", - "idtimestamp": "", - "signed_tree_head": "" - }, - "unequivocal": null - } - } - ] -}`), - }, - expected: []logverification.VerifiableEvent{ - { - EventID: "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - TenantID: "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - LeafHash: []byte{27, 214, 75, 202, 38, 121, 113, 203, 109, 24, 167, 71, 132, 29, 117, 54, 60, 78, 95, 169, 243, 246, 193, 199, 225, 59, 143, 8, 188, 135, 9, 247}, - MerkleLog: &assets.MerkleLogEntry{ - Commit: &assets.MerkleLogCommit{ - Index: 0, - Idtimestamp: "018e3f48610b089800", - }, - Confirm: &assets.MerkleLogConfirm{ - MmrSize: 7, - Root: []byte{93, 215, 30, 142, 140, 198, 116, 86, 39, 236, 148, 218, 255, 147, 212, 161, 213, 173, 154, 137, 148, 184, 107, 147, 78, 139, 140, 191, 28, 131, 122, 250}, - Timestamp: 1711106035557, - Idtimestamp: "", - SignedTreeHead: nil, - }, - }, - }, - }, - err: nil, - }, - { - name: "single event", - args: args{ - data: []byte(`{ - "identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - "asset_identity": "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8", - "event_attributes": {}, - "asset_attributes": { - "document_hash_value": "3f3cbc0b6b3b20883b8fb1bf0203b5a1233809b2ab8edc8dd00b5cf1afaae3ee" - }, - "operation": "NewAsset", - "behaviour": "AssetCreator", - "timestamp_declared": "2024-03-14T23:24:50Z", - "timestamp_accepted": "2024-03-14T23:24:50Z", - "timestamp_committed": "2024-03-22T11:13:55.557Z", - "principal_declared": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "principal_accepted": { - "issuer": "https://app.soak.stage.datatrails.ai/appidpv1", - "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", - "display_name": "Root", - "email": "" - }, - "confirmation_status": "CONFIRMED", - "transaction_id": "", - "block_number": 0, - "transaction_index": 0, - "from": "0xF17B3B9a3691846CA0533Ce01Fa3E35d6d6f714C", - "tenant_identity": "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - "merklelog_entry": { - "commit": { - "index": "0", - "idtimestamp": "018e3f48610b089800" - }, - "confirm": { - "mmr_size": "7", - "root": "XdcejozGdFYn7JTa/5PUodWtmomUuGuTTouMvxyDevo=", - "timestamp": "1711106035557", - "idtimestamp": "", - "signed_tree_head": "" - }, - "unequivocal": null - } -}`), - }, - expected: []logverification.VerifiableEvent{ - { - EventID: "assets/31de2eb6-de4f-4e5a-9635-38f7cd5a0fc8/events/21d55b73-b4bc-4098-baf7-336ddee4f2f2", - TenantID: "tenant/73b06b4e-504e-4d31-9fd9-5e606f329b51", - LeafHash: []byte{27, 214, 75, 202, 38, 121, 113, 203, 109, 24, 167, 71, 132, 29, 117, 54, 60, 78, 95, 169, 243, 246, 193, 199, 225, 59, 143, 8, 188, 135, 9, 247}, - MerkleLog: &assets.MerkleLogEntry{ - Commit: &assets.MerkleLogCommit{ - Index: 0, - Idtimestamp: "018e3f48610b089800", - }, - Confirm: &assets.MerkleLogConfirm{ - MmrSize: 7, - Root: []byte{93, 215, 30, 142, 140, 198, 116, 86, 39, 236, 148, 218, 255, 147, 212, 161, 213, 173, 154, 137, 148, 184, 107, 147, 78, 139, 140, 191, 28, 131, 122, 250}, - Timestamp: 1711106035557, - Idtimestamp: "", - SignedTreeHead: nil, - }, - }, - }, - }, - err: nil, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual, err := VerifiableEventsFromData(test.args.data) - - assert.Equal(t, test.err, err) - assert.Equal(t, len(test.expected), len(actual)) - - for index, expectedEvent := range test.expected { - actualEvent := actual[index] - - assert.Equal(t, expectedEvent.EventID, actualEvent.EventID) - assert.Equal(t, expectedEvent.TenantID, actualEvent.TenantID) - assert.Equal(t, expectedEvent.LeafHash, actualEvent.LeafHash) - - assert.Equal(t, expectedEvent.MerkleLog.Commit.Index, actualEvent.MerkleLog.Commit.Index) - assert.Equal(t, expectedEvent.MerkleLog.Commit.Idtimestamp, actualEvent.MerkleLog.Commit.Idtimestamp) - - assert.Equal(t, expectedEvent.MerkleLog.Confirm.MmrSize, actualEvent.MerkleLog.Confirm.MmrSize) - assert.Equal(t, expectedEvent.MerkleLog.Confirm.Root, actualEvent.MerkleLog.Confirm.Root) - assert.Equal(t, expectedEvent.MerkleLog.Confirm.Timestamp, actualEvent.MerkleLog.Confirm.Timestamp) - } - }) - } -} diff --git a/replicatelogs.go b/replicatelogs.go index 153b65b..b84f08e 100644 --- a/replicatelogs.go +++ b/replicatelogs.go @@ -29,6 +29,12 @@ const ( // The default data retention policy is 2 years, so this is a generous default for "all data". tenYearsOfHours = 10 * 365 * 24 * time.Hour + + // jitterRangeMS is the range from 0 to jitter in milliseconds + jitterRangeMS = 100 + + // massifHeightMax is the maximum massif height + massifHeightMax = 255 ) var ( @@ -209,7 +215,7 @@ func replicateChanges(cCtx *cli.Context, cmd *CmdCtx, changes []TenantMassif, pr var errs []error for err := range errChan { - cmd.log.Infof(err.Error()) + cmd.log.Infof("%v", err) errs = append(errs, err) } if len(errs) > 0 { @@ -239,7 +245,7 @@ func initReplication(cCtx *cli.Context, cmd *CmdCtx, change TenantMassif) (*Veri func defaultRetryDelay(_ error) time.Duration { // give the delay some jitter, this is universally a good practice - return baseDefaultRetryDelay + time.Duration(rand.Intn(100))*time.Millisecond + return baseDefaultRetryDelay + time.Duration(rand.Intn(jitterRangeMS))*time.Millisecond } func newProgressor(cCtx *cli.Context, barName string, increments int) Progresser { @@ -277,7 +283,7 @@ func NewVerifiedReplica( } massifHeight := cCtx.Int64("height") - if massifHeight > 255 { + if massifHeight > massifHeightMax { return nil, fmt.Errorf("massif height must be less than 256") } diff --git a/tests/replicatelogs/replicatelogs_azurite_test.go b/tests/replicatelogs/replicatelogs_azurite_test.go index 253b2ac..52e15b2 100644 --- a/tests/replicatelogs/replicatelogs_azurite_test.go +++ b/tests/replicatelogs/replicatelogs_azurite_test.go @@ -411,7 +411,7 @@ func (s *ReplicateLogsCmdSuite) TestAncestorMassifLogsForOneTenant() { if tt.ancestors >= massifCount-1 { // then all massifs should be replicated - for i := uint32(0); i < massifCount; i++ { + for i := range massifCount { expectMassifFile := filepath.Join(replicaDir, massifs.ReplicaRelativeMassifPath(tenantId0, i)) s.FileExistsf(expectMassifFile, "the replicated massif should exist") expectSealFile := filepath.Join(replicaDir, massifs.ReplicaRelativeSealPath(tenantId0, i)) diff --git a/tests/systemtest/test.sh b/tests/systemtest/test.sh index fd234be..f69230b 100755 --- a/tests/systemtest/test.sh +++ b/tests/systemtest/test.sh @@ -61,7 +61,7 @@ testVeracityVersion() { assertEquals "veracity --version should return a 0 exit code" 0 $? echo "$output" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+' - assertTrue "The output should start with a semantic version string" $? + assertTrue "The output should start with a semantic version string" $? } testVeracityWatchPublicFindsActivity() { @@ -162,7 +162,7 @@ testValidEventNotinMassif() { output=$(curl -sL $DATATRAILS_URL/archivist/v2/$PUBLIC_EVENT_ID | $VERACITY_INSTALL --data-local $SOAK_LOCAL_BLOB_FILE --tenant=$PROD_PUBLIC_TENANT_ID verify-included 2>&1) assertEquals "verifying an event not in the massif should result in an error" 1 $? - assertStringMatch "Error should have the correct error message" "$output" "$expected_message" + assertStringMatch "Error should have the correct error message" "$expected_message" "$output" } testNon200Response() { @@ -194,12 +194,12 @@ testNotBlobFile() { } testInvalidBlobUrl() { - local expected_message="error: unexpected end of JSON input" + local expected_message="error: no json given" local invalid_domain="https://app.datatrails.com" local invalid_url="$invalid_domain/verifiabledata" local output output=$(curl -sL $invalid_domain/archivist/v2/$PUBLIC_EVENT_ID | $VERACITY_INSTALL --data-url $invalid_url --tenant=$PROD_PUBLIC_TENANT_ID verify-included 2>&1) assertEquals "verifying an event not in the massif should result in an error" 1 $? - assertStringMatch "Error should have the correct error message" "$output" "$expected_message" + assertStringMatch "Error should have the correct error message" "$expected_message" "$output" } diff --git a/timestamp.go b/timestamp.go index d240fc1..c2f7079 100644 --- a/timestamp.go +++ b/timestamp.go @@ -5,6 +5,12 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) +const ( + millisecondMultiplier = 1000 + + nanoMultipler = 1e6 +) + func NewTimestamp(id uint64, epoch uint8) (*timestamppb.Timestamp, error) { ts := ×tamppb.Timestamp{} err := SetTimestamp(id, ts, epoch) @@ -20,8 +26,8 @@ func SetTimestamp(id uint64, ts *timestamppb.Timestamp, epoch uint8) error { return err } - ts.Seconds = ms / 1000 - ts.Nanos = int32(uint64(ms)-(uint64(ts.GetSeconds())*1000)) * 1e6 + ts.Seconds = ms / millisecondMultiplier + ts.Nanos = int32(uint64(ms)-(uint64(ts.GetSeconds())*millisecondMultiplier)) * nanoMultipler return nil } diff --git a/veracitytesting/testcontext.go b/veracitytesting/testcontext.go index 6a2b6dd..63f3351 100644 --- a/veracitytesting/testcontext.go +++ b/veracitytesting/testcontext.go @@ -81,7 +81,7 @@ func GenerateTenantLog(tc *mmrtesting.TestContext, g EventTestGenerator, eventTo mmrIndex := mc.RangeCount() // add the generated event to the mmr - _, err1 = mc.AddHashedLeaf(sha256.New(), idTimestamp, []byte(ev.TenantIdentity), []byte(ev.GetIdentity()), leafValue) + _, err1 = mc.AddHashedLeaf(sha256.New(), idTimestamp, nil, []byte(ev.TenantIdentity), []byte(ev.GetIdentity()), leafValue) if err1 != nil { if errors.Is(err1, massifs.ErrMassifFull) { var err2 error @@ -94,7 +94,7 @@ func GenerateTenantLog(tc *mmrtesting.TestContext, g EventTestGenerator, eventTo tc.T.Fatalf("unexpected err: %v", err) } - _, err1 = mc.AddHashedLeaf(sha256.New(), idTimestamp, []byte(ev.TenantIdentity), []byte(ev.GetIdentity()), leafValue) + _, err1 = mc.AddHashedLeaf(sha256.New(), idTimestamp, nil, []byte(ev.TenantIdentity), []byte(ev.GetIdentity()), leafValue) } require.Nil(tc.T, err1) diff --git a/veracitytesting/testeventgenerator.go b/veracitytesting/testeventgenerator.go index 6e5c3f5..04e9a36 100644 --- a/veracitytesting/testeventgenerator.go +++ b/veracitytesting/testeventgenerator.go @@ -23,6 +23,10 @@ import ( const ( resourceChangedProperty = "resource_changed" resourceChangeMerkleLogStoredEvent = "assetsv2merklelogeventstored" + millisecondMultiplier = int64(1000) + names = 2 + assetAttributeWords = 4 + eventAttributeWords = 6 ) type leafHasher interface { @@ -45,15 +49,18 @@ func NewAzuriteTestContext( t *testing.T, testLabelPrefix string, ) (mmrtesting.TestContext, EventTestGenerator, mmrtesting.TestConfig) { + + eventRate := 500 + cfg := mmrtesting.TestConfig{ - StartTimeMS: (1698342521) * 1000, EventRate: 500, + StartTimeMS: (1698342521) * millisecondMultiplier, EventRate: eventRate, TestLabelPrefix: testLabelPrefix, TenantIdentity: "", Container: strings.ReplaceAll(strings.ToLower(testLabelPrefix), "_", "")} leafHasher := NewLeafHasher() tc := mmrtesting.NewTestContext(t, cfg) g := NewEventTestGenerator( - t, cfg.StartTimeMS/1000, + t, cfg.StartTimeMS/millisecondMultiplier, &leafHasher, mmrtesting.TestGeneratorConfig{ StartTimeMS: cfg.StartTimeMS, @@ -97,13 +104,16 @@ func (g *EventTestGenerator) NextId() (uint64, error) { var err error var id uint64 - for i := 0; i < 2; i++ { + var attempts = 2 + var sleep = time.Millisecond * 2 + + for range attempts { id, err = g.IdState.NextID() if err != nil { if !errors.Is(err, snowflakeid.ErrOverloaded) { return 0, err } - time.Sleep(time.Millisecond * 2) + time.Sleep(sleep) } } return id, nil @@ -127,7 +137,7 @@ func (g *EventTestGenerator) GenerateLeaf(tenantIdentity string, base, i uint64) func (g *EventTestGenerator) GenerateEventBatch(count int) []*assets.EventResponse { events := make([]*assets.EventResponse, 0, count) - for i := 0; i < count; i++ { + for range count { events = append(events, g.GenerateNextEvent(mmrtesting.DefaultGeneratorTenantIdentity)) } return events @@ -135,7 +145,7 @@ func (g *EventTestGenerator) GenerateEventBatch(count int) []*assets.EventRespon func (g *EventTestGenerator) GenerateTenantEventMessageBatch(tenantIdentity string, count int) []*azbus.ReceivedMessage { msgs := make([]*azbus.ReceivedMessage, 0, count) - for i := 0; i < count; i++ { + for range count { event := assets.EventMessage{ TenantId: tenantIdentity, Event: g.GenerateNextEvent(tenantIdentity), @@ -159,7 +169,7 @@ func (g *EventTestGenerator) GenerateNextEvent(tenantIdentity string) *assets.Ev assetIdentity := g.NewAssetIdentity() assetUUID := strings.Split(assetIdentity, "/")[1] - name := strings.Join(g.WordList(2), "") + name := strings.Join(g.WordList(names), "") email := fmt.Sprintf("%s@datatrails.com", name) subject := strconv.Itoa(g.Intn(math.MaxInt)) @@ -184,14 +194,14 @@ func (g *EventTestGenerator) GenerateNextEvent(tenantIdentity string) *assets.Ev "event-attribute-0": { Value: &attribute.Attribute_StrVal{ - StrVal: g.MultiWordString(6), + StrVal: g.MultiWordString(eventAttributeWords), }, }, }, AssetAttributes: map[string]*attribute.Attribute{ "asset-attribute-0": { Value: &attribute.Attribute_StrVal{ - StrVal: g.MultiWordString(4), + StrVal: g.MultiWordString(assetAttributeWords), }, }, }, diff --git a/verifyincluded.go b/verifyincluded.go index 2cb8c83..8817eef 100644 --- a/verifyincluded.go +++ b/verifyincluded.go @@ -7,10 +7,12 @@ import ( "fmt" "strings" - "github.com/datatrails/go-datatrails-logverification/logverification" + "github.com/datatrails/go-datatrails-logverification/logverification/app" "github.com/datatrails/go-datatrails-merklelog/massifs" "github.com/datatrails/go-datatrails-merklelog/mmr" "github.com/urfave/cli/v2" + + veracityapp "github.com/datatrails/veracity/app" ) var ( @@ -30,32 +32,19 @@ func proofPath(proof [][]byte) string { return fmt.Sprintf("[%s]", strings.Join(hexProof, ", ")) } +// verifyEvent is an example function of how to verify the inclusion of a datatrails event using the mmr and massifs modules func verifyEvent( - event *logverification.VerifiableEvent, massifHeight uint8, massifReader MassifReader, - forTenant string, - tenantLogPath string, + event *app.AppEntry, logTenant string, mmrEntry []byte, massifHeight uint8, massifGetter MassifGetter, ) ([][]byte, error) { // Get the mmrIndex from the request and then compute the massif // it implies based on the massifHeight command line option. - mmrIndex := event.MerkleLog.Commit.Index + mmrIndex := event.MMRIndex() - tenantIdentity := forTenant massifIndex := massifs.MassifIndexFromMMRIndex(massifHeight, mmrIndex) - if tenantIdentity == "" { - // The tenant identity on the event is the original tenant - // that created the event. For public assets and shared - // assets, this is true regardless of which tenancy the - // record is fetched from. Those same events will appear in - // the logs of all tenants they were shared with. - tenantIdentity = event.TenantID - } - if tenantLogPath == "" { - tenantLogPath = tenantIdentity - } // read the massif blob - massif, err := massifReader.GetMassif(context.Background(), tenantLogPath, massifIndex) + massif, err := massifGetter.GetMassif(context.Background(), logTenant, massifIndex) if err != nil { return nil, err } @@ -70,7 +59,7 @@ func verifyEvent( // includes the event. Future work can deepen this to include // discovery of the log head, and or verification against a // sealed MMRSize. - verified, err := mmr.VerifyInclusion(&massif, sha256.New(), mmrSize, event.LeafHash, mmrIndex, proof) + verified, err := mmr.VerifyInclusion(&massif, sha256.New(), mmrSize, mmrEntry, mmrIndex, proof) if verified { return proof, nil } @@ -111,79 +100,100 @@ Note: for publicly attested events, or shared protected events, you must use --t cmd.log.Infof(m, args...) } - var verifiableEvents []logverification.VerifiableEvent - if cCtx.Args().Len() > 0 { - // note: to be permissive in what we accept, we just require the minimum count here. - // we do not proces multiple files if there are more than one, though we could. - verifiableEvents, err = filePathToVerifiableEvents(cCtx.Args().Get(0)) + tenantIdentity := cCtx.String("tenant") + if tenantIdentity != "" { + log("verifying for tenant: %s", tenantIdentity) } else { - verifiableEvents, err = stdinToVerifiableEvents() + log("verifying protected events for the event creator") } + + // If we are reading the massif log locally, the log path is the + // data-local path. The reader does the right thing regardless of + // whether the option is a directory or a file. + // verifyEvent defaults it to tenantIdentity for the benefit of the remote reader implementation + tenantLogPath := cCtx.String("data-local") + + if tenantLogPath == "" { + tenantLogPath = tenantIdentity + } + + appData, err := veracityapp.ReadAppData(cCtx.Args().Len() == 0, cCtx.Args().Get(0)) if err != nil { return err } - if err = cfgMassifReader(cmd, cCtx); err != nil { + verifiableLogEntries, err := veracityapp.AppDataToVerifiableLogEntries(appData, tenantIdentity) + if err != nil { return err } - tenantIdentity := cCtx.String("tenant") - if tenantIdentity != "" { - log("verifying for tenant: %s", tenantIdentity) - } else { - log("verifying protected events for the asset creator") + if err = cfgMassifReader(cmd, cCtx); err != nil { + return err } var countNotCommitted int var countVerifyFailed int - // If we are reading the massif log locally, the log path is the - // data-local path. The reader does the right thing regardless of - // whether the option is a directory or a file. - // verifyEvent defaults it to tenantIdentity for the benefit of the remote reader implementation - tenantLogPath := cCtx.String("data-local") + previousMassifIndex := uint64(0) + var massifContext *massifs.MassifContext = nil - for _, event := range verifiableEvents { + for _, event := range verifiableLogEntries { - // don't try if we don't even have any merkle log entries on this event - if event.MerkleLog == nil || event.MerkleLog.Commit == nil { - countNotCommitted += 1 - log("not committed: %s", event.EventID) - continue - } + leafIndex := mmr.LeafIndex(event.MMRIndex()) - mmrIndex := event.MerkleLog.Commit.Index - leafIndex := mmr.LeafIndex(mmrIndex) - log("verifying: %d %d %s %s", mmrIndex, leafIndex, event.MerkleLog.Commit.Idtimestamp, event.EventID) - proof, err := verifyEvent(&event, cmd.massifHeight, cmd.massifReader, tenantIdentity, tenantLogPath) - if err != nil { + // get the massif index for the event event + massifIndex := massifs.MassifIndexFromMMRIndex(cmd.massifHeight, event.MMRIndex()) - // We keep going if the error is a verification failure, as - // this supports reporting "gaps". All other errors are - // imediately terminal - if !errors.Is(err, ErrVerifyInclusionFailed) { + // check if we need this event is part of a different massif than the previous event + // + // if it is, we get the new massif + if massifContext == nil || massifIndex != previousMassifIndex { + massif, err := cmd.massifReader.GetMassif(cCtx.Context, tenantLogPath, massifIndex) + if err != nil { return err } + + massifContext = &massif + } + + verified, err := event.VerifyInclusion(massifContext) + + // We keep going if the error is a verification failure, as + // this supports reporting "gaps". All other errors are + // immediately terminal + if errors.Is(err, mmr.ErrVerifyInclusionFailed) || !verified { countVerifyFailed += 1 - log("XX|%d %d\n", mmrIndex, leafIndex) + log("XX|%d %d\n", event.MMRIndex(), leafIndex) continue } - log("OK|%d %d|%s", mmrIndex, leafIndex, proofPath(proof)) + // all other errors immediately terminal + if err != nil { + return err + } + + proof, err := event.Proof(massifContext) + if err != nil { + return err + } + + log("OK|%d %d|%s", event.MMRIndex(), leafIndex, proofPath(proof)) + + previousMassifIndex = massifIndex } if countVerifyFailed != 0 { - if len(verifiableEvents) == 1 { + if len(verifiableLogEntries) == 1 { return fmt.Errorf("%w. for tenant %s", ErrVerifyInclusionFailed, tenantIdentity) } return fmt.Errorf("%w. for tenant %s", ErrVerifyInclusionFailed, tenantIdentity) } if countNotCommitted > 0 { - if len(verifiableEvents) == 1 { + if len(verifiableLogEntries) == 1 { return fmt.Errorf("%w. not committed: %d", ErrUncommittedEvents, countNotCommitted) } - return fmt.Errorf("%w. %d events of %d were not committed", ErrUncommittedEvents, countNotCommitted, len(verifiableEvents)) + return fmt.Errorf("%w. %d events of %d were not committed", ErrUncommittedEvents, countNotCommitted, len(verifiableLogEntries)) } return nil diff --git a/verifyincluded_test.go b/verifyincluded_test.go index fb380d5..ba87bea 100644 --- a/verifyincluded_test.go +++ b/verifyincluded_test.go @@ -2,193 +2,196 @@ package veracity import ( "context" - "encoding/hex" - "errors" + "crypto/sha256" + "encoding/binary" "fmt" "testing" "github.com/datatrails/go-datatrails-common/logger" - "github.com/datatrails/go-datatrails-logverification/logverification" + "github.com/datatrails/go-datatrails-logverification/logverification/app" "github.com/datatrails/go-datatrails-merklelog/massifs" + veracityapp "github.com/datatrails/veracity/app" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -type MockMR struct { - data map[uint64][]byte -} +// testMassifContext generates a massif context with 2 entries +// +// the first entry is a known assetsv2 events +// the seconds entry is a known eventsv1 event +func testMassifContext(t *testing.T) *massifs.MassifContext { -func (*MockMR) GetFirstMassif(ctx context.Context, tenantIdentity string, opts ...massifs.ReaderOption) (massifs.MassifContext, error) { - return massifs.MassifContext{}, fmt.Errorf("not implemented") -} -func (*MockMR) GetHeadMassif(ctx context.Context, tenantIdentity string, opts ...massifs.ReaderOption) (massifs.MassifContext, error) { - return massifs.MassifContext{}, fmt.Errorf("not implemented") -} -func (*MockMR) GetLazyContext(ctx context.Context, tenantIdentity string, which massifs.LogicalBlob, opts ...massifs.ReaderOption) (massifs.LogBlobContext, uint64, error) { - return massifs.LogBlobContext{}, 0, fmt.Errorf("not implemented") -} -func (m *MockMR) GetMassif(ctx context.Context, tenantIdentity string, massifIndex uint64, opts ...massifs.ReaderOption) (massifs.MassifContext, error) { - mc := massifs.MassifContext{} - data, ok := m.data[massifIndex] - if !ok { - return mc, fmt.Errorf("massif not found") + start := massifs.MassifStart{ + MassifHeight: 3, } - mc.Data = data - return mc, nil -} -func (m *MockMR) GetHeadVerifiedContext( - ctx context.Context, tenantIdentity string, - opts ...massifs.ReaderOption, -) (*massifs.VerifiedContext, error) { - return nil, errors.New("not implemented") + testMassifContext := &massifs.MassifContext{ + Start: start, + LogBlobContext: massifs.LogBlobContext{ + BlobPath: "test", + Tags: map[string]string{}, + }, + } + + data, err := start.MarshalBinary() + require.NoError(t, err) + + testMassifContext.Data = append(data, testMassifContext.InitIndexData()...) + + testMassifContext.Tags["firstindex"] = fmt.Sprintf("%016x", testMassifContext.Start.FirstIndex) + + hasher := sha256.New() + + // KAT Data taken from an actual merklelog. + + // AssetsV2 + _, err = testMassifContext.AddHashedLeaf( + hasher, + binary.BigEndian.Uint64([]byte{148, 111, 227, 95, 198, 1, 121, 0}), + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + []byte("tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0"), + []byte("assets/899e00a2-29bc-4316-bf70-121ce2044472/events/450dce94-065e-4f6a-bf69-7b59f28716b6"), + []byte{97, 231, 1, 42, 127, 20, 181, 70, 122, 134, 84, 231, 174, 117, 200, 148, 171, 205, 57, 146, 174, 48, 34, 30, 152, 215, 77, 3, 204, 14, 202, 57}, + ) + require.NoError(t, err) + + // EventsV1 + _, err = testMassifContext.AddHashedLeaf( + hasher, + binary.BigEndian.Uint64([]byte{148, 112, 0, 54, 17, 1, 121, 0}), + []byte{1, 17, 39, 88, 206, 168, 203, 73, 36, 141, 248, 252, 186, 30, 49, 248, 176, 0, 0, 0, 0, 0, 0, 0}, + []byte("tenant/112758ce-a8cb-4924-8df8-fcba1e31f8b0"), + []byte("events/01947000-3456-780f-bfa9-29881e3bac88"), + []byte{215, 191, 107, 210, 134, 10, 40, 56, 226, 71, 136, 164, 9, 118, 166, 159, 86, 31, 175, 135, 202, 115, 37, 151, 174, 118, 115, 113, 25, 16, 144, 250}, + ) + require.NoError(t, err) + + // Intermediate Node Skipped + + return testMassifContext } -func (m *MockMR) GetVerifiedContext( - ctx context.Context, tenantIdentity string, massifIndex uint64, - opts ...massifs.ReaderOption, -) (*massifs.VerifiedContext, error) { - return nil, errors.New("not implemented") +type fakeMassifGetter struct { + t *testing.T + massifContext *massifs.MassifContext } -func NewMockMR(massifIndex uint64, data string) *MockMR { - b, e := hex.DecodeString(data) - if e != nil { - return nil +// NewFakeMassifGetter creates a new massif getter that has 2 entries in the massif it gets +// +// one assetsv2 event entry and one eventsv1 entry +func NewFakeMassifGetter(t *testing.T) *fakeMassifGetter { + + massifContext := testMassifContext(t) + + return &fakeMassifGetter{ + t: t, + massifContext: massifContext, } - return &MockMR{ - data: map[uint64][]byte{massifIndex: b}, + +} + +// NewFakeMassifGetterInvalidRoot creates a new massif getter that has an incorrect massif root +func NewFakeMassifGetterInvalidRoot(t *testing.T) *fakeMassifGetter { + + massifContext := testMassifContext(t) + + // a massif context with 2 entries has its root at index 2 + // + // 2 + // / \ + // 0 1 + rootMMRIndex := 2 + + rootDataStart := (massifContext.LogStart() + uint64(rootMMRIndex*massifs.LogEntryBytes)) - 1 + rootDataEnd := (rootDataStart + massifs.ValueBytes) + + // set the start and end of the root entry to 0 + // to make the root entry invalid + massifContext.Data[rootDataStart] = 0x0 + massifContext.Data[rootDataEnd] = 0x0 + + return &fakeMassifGetter{ + t: t, + massifContext: massifContext, } } -func TestVerifyEvent(t *testing.T) { +// GetMassif always returns the test massif +func (tmg *fakeMassifGetter) GetMassif( + ctx context.Context, + tenantIdentity string, + massifIndex uint64, + opts ...massifs.ReaderOption, +) (massifs.MassifContext, error) { + return *tmg.massifContext, nil +} + +func TestVerifyAssetsV2Event(t *testing.T) { logger.New("TestVerifyList") defer logger.OnExit() - event := []byte(`{ - "identity": "publicassets/87dd2e5a-42b4-49a5-8693-97f40a5af7f8/events/a022f458-8e55-4d63-a200-4172a42fc2aa", - "asset_identity": "publicassets/87dd2e5a-42b4-49a5-8693-97f40a5af7f8", - "event_attributes": { - "arc_access_policy_asset_attributes_read": [ - {"attribute" :"*","0x4609ea6bbe85F61bc64760273ce6D89A632B569f" :"wallet","SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ=" :"tessera"}], - "arc_access_policy_event_arc_display_type_read": [ - {"SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ=" :"tessera","value" :"*","0x4609ea6bbe85F61bc64760273ce6D89A632B569f" :"wallet"}], - "arc_access_policy_always_read": [ - {"wallet" :"0x0E29670b420B7f2E8E699647b632cdE49D868dA7","tessera" :"SmL4PHAHXLdpkj/c6Xs+2br+hxqLmhcRk75Hkj5DyEQ="}] - }, - "asset_attributes": {"arc_display_name": "Dava Derby", "arc_display_type": "public-test"}, - "operation": "NewAsset", - "behaviour": "AssetCreator", - "timestamp_declared": "2024-05-24T07:26:58Z", - "timestamp_accepted": "2024-05-24T07:26:58Z", - "timestamp_committed": "2024-05-24T07:27:00.200Z", - "principal_declared": {"issuer":"", "subject":"", "display_name":"", "email":""}, - "principal_accepted": {"issuer":"", "subject":"", "display_name":"", "email":""}, - "confirmation_status": "CONFIRMED", - "transaction_id": "0xc891533b1806555fff9ab853cd9ce1bb2c00753609070a875a44ec53a6c1213b", - "block_number": 7932, - "transaction_index": 1, - "from": "0x0E29670b420B7f2E8E699647b632cdE49D868dA7", - "tenant_identity": "tenant/7dfaa5ef-226f-4f40-90a5-c015e59998a8", - "merklelog_entry": {"commit":{"index":"0", "idtimestamp":"018fa97ef269039b00"}, - "confirm":{ - "mmr_size":"7", - "root":"/rlMNJhlay9CUuO3LgX4lSSDK6dDhtKesCO50CtrHr4=", - "timestamp":"1716535620409", - "idtimestamp":"", - "signed_tree_head":""}, - "unequivocal":null} - }`) - - eventOK, _ := logverification.NewVerifiableEvent(event) - - justDecode := func(in string) []byte { - b, _ := hex.DecodeString(in) - return b - } + + events, _ := veracityapp.NewAssetsV2AppEntries(assetsV2SingleEventList) + require.NotZero(t, len(events)) + + event := events[0] tests := []struct { name string - event *logverification.VerifiableEvent - massifReader MassifReader + event *app.AppEntry + massifGetter MassifGetter expectedProof [][]byte expectedError bool }{ { - name: "smiple OK", - event: eventOK, - massifReader: NewMockMR(0, - // 7 - // 3 6 - // 1 2 4 5 - "000000000000000090757516a9086b0000000000000000000000010e00000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "bfc511ab1b880b24bb2358e07472e3383cdeddfbc4de9d66d652197dfb2b6633"+ // this is hash(event) - "0000000000000000000000000000000000000000000000000000000000000002"+ // leaf 2 - "dbdc36cf2b46382c810ecef9a423bf7e7f222d72c221baf2e840cc94428e966c"+ // this is node 1-2 hash(3 + hash(event) + leaf 2) - "0000000000000000000000000000000000000000000000000000000000000004"+ // leaf 3 - "0000000000000000000000000000000000000000000000000000000000000005"+ // leaf 4 - "0000000000000000000000000000000000000000000000000000000000000006"+ // node 3 - 4 - "c1dc2d0cf9982d94f97597193cce3a42c21a1b02c346c0fada0aa1d48ed2089f", // this is root hash(7 + node 1-2 + node 3-4) - ), + name: "simple OK", + event: &event, + massifGetter: NewFakeMassifGetter(t), expectedError: false, expectedProof: [][]byte{ - justDecode("0000000000000000000000000000000000000000000000000000000000000002"), - justDecode("0000000000000000000000000000000000000000000000000000000000000006"), + { + 0xd7, 0xbf, 0x6b, 0xd2, 0x86, 0xa, 0x28, 0x38, + 0xe2, 0x47, 0x88, 0xa4, 0x9, 0x76, 0xa6, 0x9f, + 0x56, 0x1f, 0xaf, 0x87, 0xca, 0x73, 0x25, 0x97, + 0xae, 0x76, 0x73, 0x71, 0x19, 0x10, 0x90, 0xfa, + }, }, }, - { + /**{ name: "No mmr log", - event: eventOK, - massifReader: NewMockMR(6, "000000000000000090757516a9086b0000000000000000000000010e00000000"), + event: &event, + massifGetter: &fakeMassifGetter{t, nil}, expectedError: true, - }, + },*/ { - name: "Not valid proof", - event: eventOK, - massifReader: NewMockMR(0, - // 7 - // 3 6 - // 1 2 4 5 - "000000000000000090757516a9086b0000000000000000000000010e00000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "0000000000000000000000000000000000000000000000000000000000000000"+ - "bfc511ab1b880b24bb2358e07472e3383cdeddfbc4de9d66d652197dfb2b6633"+ // this is hash(event) - "0000000000000000000000000000000000000000000000000000000000000002"+ // leaf 2 - "dbdc36cf2b46382c810ecef9a423bf7e7f222d72c221baf2e840cc94428e966c"+ // this is node 1-2 hash(3 + hash(event) + leaf 2) - "0000000000000000000000000000000000000000000000000000000000000004"+ // leaf 3 - "0000000000000000000000000000000000000000000000000000000000000005"+ // leaf 4 - "0000000000000000000000000000000000000000000000000000000000000006"+ // node 3 - 4 - "c1dc2d0cf9982d94f97597193cce3a42c21a1b02c346c0fada0aa1d48ed208ff", // this is fake root hash - end if ff instead of 9f - ), + name: "Not valid proof", + event: &event, + massifGetter: NewFakeMassifGetterInvalidRoot(t), expectedError: true, }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - proof, err := verifyEvent(tc.event, defaultMassifHeight, tc.massifReader, "", "") + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + logTenant, err := test.event.LogTenant() + require.Nil(t, err) + + massifIndex := massifs.MassifIndexFromMMRIndex(defaultMassifHeight, test.event.MMRIndex()) + + ctx := context.Background() + massif, err := test.massifGetter.GetMassif(ctx, logTenant, massifIndex) + require.NoError(t, err) + + mmrEntry, err := test.event.MMREntry(&massif) + require.NoError(t, err) + + proof, err := verifyEvent(test.event, logTenant, mmrEntry, defaultMassifHeight, test.massifGetter) - if tc.expectedError { + if test.expectedError { assert.NotNil(t, err, "expected error got nil") } else { assert.Nil(t, err, "unexpected error") - assert.Equal(t, tc.expectedProof, proof) + assert.Equal(t, test.expectedProof, proof) } }) } diff --git a/watch_test.go b/watch_test.go index fc43217..826f622 100644 --- a/watch_test.go +++ b/watch_test.go @@ -436,7 +436,7 @@ func TestWatchForChanges(t *testing.T) { } if tt.wantOutputs != nil { reporter := tt.args.reporter.(*mockReporter) - for i := 0; i < len(tt.wantOutputs); i++ { + for i := range tt.wantOutputs { if i >= len(reporter.outf) { t.Errorf("wanted %d outputs, got %d", len(tt.wantOutputs), len(reporter.outf)) break @@ -469,7 +469,7 @@ func newFilterBlobItems(nameAndLastIdPairs ...string) []*azStorageBlob.FilterBlo // just ignore odd lenght var items []*azStorageBlob.FilterBlobItem pairs := len(nameAndLastIdPairs) >> 1 - for i := 0; i < pairs; i++ { + for i := range pairs { name := nameAndLastIdPairs[i*2] lastid := nameAndLastIdPairs[i*2+1] items = append(items, newFilterBlobItem(name, lastid))