├── .codecov.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_template.yml │ ├── feature_template.yml │ └── question_template.yml ├── renovate.json └── workflows │ ├── go.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .releaserc.js ├── .tool-versions ├── LICENSE ├── Makefile ├── README.md ├── accesscontroller ├── default.go └── interface.go ├── enc ├── box.go ├── box_test.go └── iface.go ├── entry ├── entry.go ├── entry_io.go ├── entry_map.go ├── fetcher.go ├── lamportclock.go ├── queue.go ├── sorting │ └── sorting.go └── utils.go ├── errmsg └── errmsg.go ├── example ├── doc.go ├── example_log_append_test.go ├── go.mod └── go.sum ├── go.mod ├── go.sum ├── identityprovider ├── identities.go ├── identity.go ├── interface.go └── orbitdb.go ├── iface ├── doc.go └── iface.go ├── io ├── cbor │ ├── cbor.go │ └── cid.go ├── compatibility.go ├── jsonable │ └── types.go └── pb │ └── pb.go ├── keystore ├── interface.go └── keystore.go ├── log.go ├── log_io.go └── test ├── bench_add_test.go ├── bench_join_test.go ├── entry_io_test.go ├── entry_test.go ├── goleak_test.go ├── log_append_test.go ├── log_crdt_test.go ├── log_heads_tails_test.go ├── log_iterator_test.go ├── log_join_concurrent_test.go ├── log_join_test.go ├── log_load_test.go ├── log_references_test.go ├── log_test.go ├── signed_log_test.go ├── utils.go ├── utils_fixtures_test.go ├── utils_logcreator_test.go └── utils_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "1...100" 9 | status: 10 | project: no 11 | patch: no 12 | changes: no 13 | 14 | ignore: 15 | - "**/*.gen.go" 16 | - "**/generated.go" 17 | - "**/*.pb.go" 18 | - "**/*.pb.gw.go" 19 | - "**/gen/**" 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_template.yml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: Report a bug for this project. 3 | labels: ["bug"] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: Is there an existing issue for this? 8 | description: Please search to see if an issue already exists for the bug you encountered. 9 | options: 10 | - label: I have searched the existing issues 11 | required: true 12 | - type: input 13 | id: package-version 14 | attributes: 15 | label: Package version 16 | description: What version of go-ipfs-log package are you using? 17 | placeholder: v1.8.0 18 | validations: 19 | required: true 20 | - type: input 21 | id: go-version 22 | attributes: 23 | label: Golang version 24 | description: What version of Golang are you using? 25 | placeholder: go1.18.4 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: bug-description 30 | attributes: 31 | label: Bug description 32 | description: Provide a bug description and a code snippet if applicable. 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: current-behavior 37 | attributes: 38 | label: Current behavior 39 | description: A output after code execution including stack traces, debug logs, etc. 40 | placeholder: Currently ... 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: expected-behavior 45 | attributes: 46 | label: Expected behavior 47 | description: Please provide what would be your expectation to happen. 48 | placeholder: In this situation, the output of the code should ... 49 | validations: 50 | required: true 51 | - type: textarea 52 | id: environment 53 | attributes: 54 | label: Environment 55 | description: What is your development environment? 56 | placeholder: Linux Ubuntu v20.04 57 | validations: 58 | required: true 59 | - type: textarea 60 | id: other 61 | attributes: 62 | label: Other 63 | placeholder: Any other detail? 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_template.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: Suggest an idea for this project. 3 | labels: [":rocket: feature-request"] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: Is there an existing issue for this? 8 | description: Please search to see if an issue already exists for this feature request. 9 | options: 10 | - label: I have searched the existing issues 11 | required: true 12 | - type: textarea 13 | id: feature 14 | attributes: 15 | label: Feature request 16 | description: Provide a detailed description of the change or addition you are proposing. 17 | placeholder: There should be ... 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: context 22 | attributes: 23 | label: Context 24 | description: Why is this change important to you? How would you use it? How can it benefit other users? 25 | placeholder: This feature request is important because ... 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: implementation 30 | attributes: 31 | label: Possible implementation 32 | description: Not obligatory, but suggest an idea for implementing addition or change. 33 | placeholder: This feature could be implemented by ... 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question_template.yml: -------------------------------------------------------------------------------- 1 | name: "Question" 2 | description: Ask a question about this project. 3 | labels: ["question"] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: Asking a question, not reporting a bug 8 | description: If your question is "Why did I get this error?" and you think it is a bug, then please open a Bug Report at https://github.com/berty/go-ipfs-log/issues/new/ 9 | options: 10 | - label: This question is not about a bug 11 | required: true 12 | - type: checkboxes 13 | attributes: 14 | label: Is there an existing issue for this? 15 | description: Please search to see if an issue already exists for your question. 16 | options: 17 | - label: I have searched the existing issues 18 | required: true 19 | - type: textarea 20 | id: question 21 | attributes: 22 | label: Question 23 | description: Provide your question with enough detail that it is helpful to anyone reading the question (maybe years later). 24 | placeholder: How do I ... 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: context 29 | attributes: 30 | label: Context 31 | description: Is it a general question about the design and goals of go-ipfs-log, or about how to use use it in an app (or some other context)? 32 | placeholder: This is a question about ... 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "groupName": "all", 6 | "gomodTidy": true 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | 10 | jobs: 11 | golangci-lint: 12 | name: "GolangCI-lint" 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | golang: 17 | - "1.21.x" 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.golang }} 24 | cache: false 25 | - name: golangci-lint 26 | uses: golangci/golangci-lint-action@v4 27 | with: 28 | version: v1.54 29 | args: --timeout=10m 30 | # only-new-issues: true 31 | 32 | go-tests-on-linux: 33 | runs-on: ubuntu-latest 34 | strategy: 35 | matrix: 36 | golang: 37 | - "1.21" 38 | - "1.22" 39 | env: 40 | OS: ubuntu-latest 41 | GOLANG: ${{ matrix.golang }} 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@master 45 | - name: Set up Go 46 | uses: actions/setup-go@v4 47 | with: 48 | go-version: ${{ matrix.golang }} 49 | - name: Cache Go modules 50 | uses: actions/cache@v1 51 | with: 52 | path: ~/go/pkg/mod 53 | key: ${{ runner.os }}-go-${{matrix.golang}}-${{ hashFiles('**/go.sum') }} 54 | restore-keys: | 55 | ${{ runner.os }}-go-${{matrix.golang}}- 56 | - name: Download modules 57 | run: go mod download 58 | - name: Check go.mod and go.sum 59 | run: | 60 | go mod tidy -v 61 | git --no-pager diff go.mod go.sum 62 | git --no-pager diff --quiet go.mod go.sum 63 | - name: Build tests 64 | run: go test -i ./... 65 | - name: Run tests with race, without goleak 66 | run: go test -race -covermode=atomic -coverpkg=all -coverprofile=coverage.txt -cover -v ./... -test.timeout=10m 67 | # temporarily disabled, because not stable enough 68 | #- name: Run tests without race, with goleak 69 | # run: go test -v -tags=goleak -test.timeout=10m ./... 70 | - name: Upload coverage to Codecov 71 | uses: codecov/codecov-action@v1 72 | with: 73 | file: ./coverage.txt 74 | flags: unittests 75 | env_vars: OS,GOLANG 76 | name: codecov-umbrella 77 | fail_ci_if_error: false 78 | go-tests-on-macos: 79 | runs-on: macos-latest 80 | strategy: 81 | matrix: 82 | golang: 83 | - "1.21" 84 | - "1.22" 85 | env: 86 | OS: macos-latest 87 | GOLANG: ${{ matrix.golang }} 88 | steps: 89 | - name: Checkout 90 | uses: actions/checkout@master 91 | - name: Set up Go 92 | uses: actions/setup-go@v4 93 | with: 94 | go-version: ${{ matrix.golang }} 95 | - name: Cache Go modules 96 | uses: actions/cache@v1 97 | with: 98 | path: ~/go/pkg/mod 99 | key: ${{ runner.os }}-${{matrix.golang}}-go-${{ hashFiles('**/go.sum') }} 100 | restore-keys: | 101 | ${{ runner.os }}-${{matrix.golang}}-go- 102 | - name: Download modules 103 | run: go mod download 104 | - name: Check go.mod and go.sum 105 | run: | 106 | go mod tidy -v 107 | git --no-pager diff go.mod go.sum 108 | git --no-pager diff --quiet go.mod go.sum 109 | - name: Build tests 110 | run: go test -i ./... 111 | - name: Run tests with race, without goleak 112 | run: go test -race -covermode=atomic -coverpkg=all -coverprofile=coverage.txt -cover -v ./... -test.timeout=10m 113 | # temporarily disabled, because not stable enough 114 | #- name: Run tests without race, with goleak 115 | # run: go test -v -tags=goleak -test.timeout=10m ./... 116 | - name: Upload coverage to Codecov 117 | uses: codecov/codecov-action@v1 118 | with: 119 | file: ./coverage.txt 120 | flags: unittests 121 | env_vars: OS,GOLANG 122 | name: codecov-umbrella 123 | fail_ci_if_error: false 124 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: push 4 | 5 | jobs: 6 | semantic-release: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@master 10 | - uses: codfish/semantic-release-action@v1 11 | if: github.ref == 'refs/heads/master' 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Vim template 3 | # Swap 4 | [._]*.s[a-v][a-z] 5 | [._]*.sw[a-p] 6 | [._]s[a-rt-v][a-z] 7 | [._]ss[a-gi-z] 8 | [._]sw[a-p] 9 | 10 | coverage.txt 11 | 12 | # osx 13 | .DS_Store 14 | 15 | # Session 16 | Session.vim 17 | 18 | # Temporary 19 | .netrwhist 20 | *~ 21 | # Auto-generated tag files 22 | tags 23 | # Persistent undo 24 | [._]*.un~ 25 | ### JetBrains template 26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 27 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 28 | 29 | # User-specific stuff 30 | .idea/**/workspace.xml 31 | .idea/**/tasks.xml 32 | .idea/**/usage.statistics.xml 33 | .idea/**/dictionaries 34 | .idea/**/shelf 35 | 36 | # Sensitive or high-churn files 37 | .idea/**/dataSources/ 38 | .idea/**/dataSources.ids 39 | .idea/**/dataSources.local.xml 40 | .idea/**/sqlDataSources.xml 41 | .idea/**/dynamic.xml 42 | .idea/**/uiDesigner.xml 43 | .idea/**/dbnavigator.xml 44 | 45 | # Gradle 46 | .idea/**/gradle.xml 47 | .idea/**/libraries 48 | 49 | # Gradle and Maven with auto-import 50 | # When using Gradle or Maven with auto-import, you should exclude module files, 51 | # since they will be recreated, and may cause churn. Uncomment if using 52 | # auto-import. 53 | # .idea/modules.xml 54 | # .idea/*.iml 55 | # .idea/modules 56 | 57 | # CMake 58 | cmake-build-*/ 59 | 60 | # Mongo Explorer plugin 61 | .idea/**/mongoSettings.xml 62 | 63 | # File-based project format 64 | *.iws 65 | 66 | # IntelliJ 67 | out/ 68 | 69 | # mpeltonen/sbt-idea plugin 70 | .idea_modules/ 71 | 72 | # JIRA plugin 73 | atlassian-ide-plugin.xml 74 | 75 | # Cursive Clojure plugin 76 | .idea/replstate.xml 77 | 78 | # Crashlytics plugin (for Android Studio and IntelliJ) 79 | com_crashlytics_export_strings.xml 80 | crashlytics.properties 81 | crashlytics-build.properties 82 | fabric.properties 83 | 84 | # Editor-based Rest Client 85 | .idea/httpRequests 86 | ### Go template 87 | # Binaries for programs and plugins 88 | *.exe 89 | *.exe~ 90 | *.dll 91 | *.so 92 | *.dylib 93 | 94 | # Test binary, build with `go test -c` 95 | *.test 96 | 97 | # Output of the go coverage tool, specifically when used with LiteIDE 98 | *.out 99 | ### Emacs template 100 | # -*- mode: gitignore; -*- 101 | *~ 102 | \#*\# 103 | /.emacs.desktop 104 | /.emacs.desktop.lock 105 | *.elc 106 | auto-save-list 107 | tramp 108 | .\#* 109 | 110 | # Org-mode 111 | .org-id-locations 112 | *_archive 113 | 114 | # flymake-mode 115 | *_flymake.* 116 | 117 | # eshell files 118 | /eshell/history 119 | /eshell/lastdir 120 | 121 | # elpa packages 122 | /elpa/ 123 | 124 | # reftex files 125 | *.rel 126 | 127 | # AUCTeX auto folder 128 | /auto/ 129 | 130 | # cask packages 131 | .cask/ 132 | dist/ 133 | 134 | # Flycheck 135 | flycheck_*.el 136 | 137 | # server auth directory 138 | /server/ 139 | 140 | # projectiles files 141 | .projectile 142 | 143 | # directory configuration 144 | .dir-locals.el 145 | 146 | vendor 147 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 1m 3 | tests: false 4 | skip-files: 5 | - "test/.*" 6 | - "test/.*/.*" 7 | 8 | linters-settings: 9 | golint: 10 | min-confidence: 0 11 | maligned: 12 | suggest-new: true 13 | goconst: 14 | min-len: 5 15 | min-occurrences: 4 16 | misspell: 17 | locale: US 18 | 19 | linters: 20 | disable-all: true 21 | enable: 22 | - goconst 23 | - misspell 24 | - errcheck 25 | - unused 26 | - staticcheck 27 | - unconvert 28 | - gofmt 29 | - goimports 30 | - revive 31 | - ineffassign 32 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branch: 'master', 3 | plugins: [ 4 | '@semantic-release/commit-analyzer', 5 | '@semantic-release/release-notes-generator', 6 | '@semantic-release/github', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.22.5 2 | golangci-lint 1.54.2 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: test lint 3 | 4 | .PHONY: lint 5 | lint: 6 | golangci-lint run -v ./... 7 | 8 | .PHONY: test 9 | test: 10 | go test -race -covermode=atomic -coverpkg=all -coverprofile=coverage.txt -cover -v ./... -test.timeout=5m 11 | cd example && go test -v ./... 12 | 13 | .PHONY: goleak 14 | goleak: 15 | go test -tags=goleak -v ./... 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | go-ipfs-log 4 |
5 |

6 | 7 |

🤝 Go version of append-only log CRDT on IPFS.

8 | 9 |

10 | 11 | 12 | 13 | Code Factor 15 | 16 | 17 | Go Report Card 19 | 20 | 21 | GitHub version 23 | 24 | 25 | GoDoc 27 | 28 |

29 | 30 |

31 | berty.tech • 32 | GitHub 33 |

34 | 35 | > An append-only log on IPFS. 36 | 37 | [ipfs-log](https://github.com/orbitdb/ipfs-log/) was originally created for [orbit-db](https://github.com/orbitdb/orbit-db) - a distributed peer-to-peer database on [IPFS](https://github.com/ipfs/ipfs). This library intends to provide a fully compatible port of the JavaScript version in Go. 38 | 39 | The majority of this code was vastly derived from the JavaScript's [ipfs-log](https://github.com/orbitdb/ipfs-log) library. 40 | 41 | ## Usage 42 | 43 | See the `example` package on [GoDoc](https://godoc.org/berty.tech/go-ipfs-log/example#example-package--LogAppend) 44 | 45 | ## Licensing 46 | 47 | *go-ipfs-log* is licensed under the Apache License, Version 2.0. 48 | See [LICENSE](LICENSE) for the full license text. 49 | -------------------------------------------------------------------------------- /accesscontroller/default.go: -------------------------------------------------------------------------------- 1 | // Package accesscontroller defines a default access controller for IPFS Log, it won't actually check anything. 2 | package accesscontroller // import "berty.tech/go-ipfs-log/accesscontroller" 3 | 4 | import ( 5 | "berty.tech/go-ipfs-log/identityprovider" 6 | ) 7 | 8 | type Default struct { 9 | } 10 | 11 | // CanAppend Checks whether a given identity can append an entry to the log. 12 | // This implementation allows anyone to write to the log. 13 | func (d *Default) CanAppend(LogEntry, identityprovider.Interface, CanAppendAdditionalContext) error { 14 | return nil 15 | } 16 | 17 | var _ Interface = &Default{} 18 | -------------------------------------------------------------------------------- /accesscontroller/interface.go: -------------------------------------------------------------------------------- 1 | package accesscontroller // import "berty.tech/go-ipfs-log/accesscontroller" 2 | 3 | import ( 4 | "berty.tech/go-ipfs-log/identityprovider" 5 | ) 6 | 7 | type LogEntry interface { 8 | GetPayload() []byte 9 | GetIdentity() *identityprovider.Identity 10 | } 11 | 12 | type CanAppendAdditionalContext interface { 13 | GetLogEntries() []LogEntry 14 | } 15 | 16 | type Interface interface { 17 | CanAppend(LogEntry, identityprovider.Interface, CanAppendAdditionalContext) error 18 | } 19 | -------------------------------------------------------------------------------- /enc/box.go: -------------------------------------------------------------------------------- 1 | package enc 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | 7 | "berty.tech/go-ipfs-log/errmsg" 8 | "golang.org/x/crypto/nacl/secretbox" 9 | "golang.org/x/crypto/sha3" 10 | ) 11 | 12 | const ( 13 | SecretBoxNonceSize = 24 14 | SecretBoxKeySize = 32 15 | ) 16 | 17 | var ( 18 | ErrInvalidKey = fmt.Errorf("invalid key") 19 | ErrInvalidNonce = fmt.Errorf("invalid nonce") 20 | ErrCannotDecrypt = fmt.Errorf("unable to decrypt message") 21 | ErrCannotEncrypt = fmt.Errorf("unable to encrypt message") 22 | ) 23 | 24 | type boxed [SecretBoxKeySize]byte 25 | 26 | func (s *boxed) DeriveNonce(input []byte) ([]byte, error) { 27 | nonce := make([]byte, SecretBoxNonceSize) 28 | hash := sha3.New256() 29 | 30 | if _, err := hash.Write(input); err != nil { 31 | return nil, errmsg.ErrEncrypt.Wrap(fmt.Errorf("unable to compute a valid nonce: %w", err)) 32 | } 33 | 34 | sum := hash.Sum(nil) 35 | 36 | for i := 0; i < SecretBoxNonceSize; i++ { 37 | nonce[i] = sum[i] 38 | } 39 | 40 | return nonce, nil 41 | } 42 | 43 | func (s *boxed) Open(payload []byte) ([]byte, error) { 44 | if len(payload) < (secretbox.Overhead + SecretBoxNonceSize) { 45 | return nil, ErrCannotDecrypt 46 | } 47 | 48 | return s.OpenWithNonce(payload[SecretBoxNonceSize:], payload[0:SecretBoxNonceSize]) 49 | } 50 | 51 | func (s *boxed) Seal(encrypted []byte) ([]byte, error) { 52 | var nonce = make([]byte, SecretBoxNonceSize) 53 | 54 | size, err := rand.Read(nonce[:]) 55 | if size != SecretBoxNonceSize { 56 | err = fmt.Errorf("size read: %d (required %d)", size, SecretBoxNonceSize) 57 | } 58 | if err != nil { 59 | return nil, fmt.Errorf("%w", err) 60 | } 61 | 62 | sealed, err := s.SealWithNonce(encrypted, nonce) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return append(nonce, sealed...), nil 68 | } 69 | 70 | func (s *boxed) OpenWithNonce(payload []byte, nonce []byte) ([]byte, error) { 71 | if len(nonce) != SecretBoxNonceSize { 72 | return nil, ErrInvalidNonce 73 | } 74 | 75 | nonceArr := [SecretBoxNonceSize]byte{} 76 | for i, c := range nonce { 77 | nonceArr[i] = c 78 | } 79 | 80 | dec, ok := secretbox.Open(nil, payload, &nonceArr, (*[32]byte)(s)) 81 | if !ok || dec == nil { 82 | return nil, ErrCannotDecrypt 83 | } 84 | 85 | return dec, nil 86 | } 87 | 88 | func (s *boxed) SealWithNonce(encrypted []byte, nonce []byte) ([]byte, error) { 89 | if len(nonce) != SecretBoxNonceSize { 90 | return nil, ErrInvalidNonce 91 | } 92 | 93 | nonceArr := [SecretBoxNonceSize]byte{} 94 | for i, c := range nonce { 95 | nonceArr[i] = c 96 | } 97 | 98 | enc := secretbox.Seal(nil, encrypted, &nonceArr, (*[32]byte)(s)) 99 | if enc == nil { 100 | return nil, ErrCannotEncrypt 101 | } 102 | 103 | return enc, nil 104 | } 105 | 106 | func NewSecretbox(key []byte) (SharedKey, error) { 107 | if len(key) != SecretBoxKeySize { 108 | return nil, ErrInvalidKey 109 | } 110 | 111 | var keyArr [SecretBoxKeySize]byte 112 | for i, c := range key { 113 | keyArr[i] = c 114 | } 115 | 116 | return (*boxed)(&keyArr), nil 117 | } 118 | -------------------------------------------------------------------------------- /enc/box_test.go: -------------------------------------------------------------------------------- 1 | package enc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestNewSecretbox(t *testing.T) { 10 | boxed1, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}) 11 | require.NoError(t, err) 12 | require.NotNil(t, boxed1) 13 | 14 | boxed1, err = NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1}) 15 | require.Error(t, err) 16 | require.Nil(t, boxed1) 17 | 18 | boxed1, err = NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3}) 19 | require.Error(t, err) 20 | require.Nil(t, boxed1) 21 | 22 | boxed1, err = NewSecretbox(nil) 23 | require.Error(t, err) 24 | require.Nil(t, boxed1) 25 | } 26 | 27 | func TestBoxed_Seal_Open(t *testing.T) { 28 | boxed1, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}) 29 | require.NoError(t, err) 30 | 31 | boxed2, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3}) 32 | require.NoError(t, err) 33 | 34 | ref1 := []byte("test1") 35 | 36 | sealed1, err := boxed1.Seal(ref1) 37 | require.NoError(t, err) 38 | 39 | sealed1Other, err := boxed1.Seal(ref1) 40 | require.NoError(t, err) 41 | 42 | require.NotEqual(t, sealed1, sealed1Other) 43 | 44 | opened1, err := boxed1.Open(sealed1) 45 | require.NoError(t, err) 46 | require.Equal(t, ref1, opened1) 47 | 48 | opened2, err := boxed2.Open(sealed1) 49 | require.Error(t, err) 50 | require.Nil(t, opened2) 51 | 52 | opened1Other, err := boxed1.Open(sealed1Other) 53 | require.NoError(t, err) 54 | require.Equal(t, ref1, opened1Other) 55 | 56 | opened1, err = boxed1.Open(sealed1[0 : len(sealed1)-2]) 57 | require.Error(t, err) 58 | require.Nil(t, opened1) 59 | } 60 | 61 | func TestBoxed_SealWithNonce_OpenWithNonce(t *testing.T) { 62 | boxed1, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}) 63 | require.NoError(t, err) 64 | 65 | boxed2, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3}) 66 | require.NoError(t, err) 67 | 68 | ref1 := []byte("test1") 69 | nonce1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3} 70 | nonce2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 4} 71 | 72 | sealed1, err := boxed1.SealWithNonce(ref1, nonce1) 73 | require.NoError(t, err) 74 | 75 | sealed1Other, err := boxed1.SealWithNonce(ref1, nonce2) 76 | require.NoError(t, err) 77 | require.NotEqual(t, ref1, sealed1Other) 78 | 79 | sealed1Other, err = boxed1.SealWithNonce(ref1, nonce1) 80 | require.NoError(t, err) 81 | require.Equal(t, sealed1, sealed1Other) 82 | 83 | opened1, err := boxed1.OpenWithNonce(sealed1, nonce1) 84 | require.NoError(t, err) 85 | require.Equal(t, ref1, opened1) 86 | 87 | opened2, err := boxed2.OpenWithNonce(sealed1, nonce1) 88 | require.Error(t, err) 89 | require.Nil(t, opened2) 90 | 91 | opened2, err = boxed1.OpenWithNonce(sealed1, nonce2) 92 | require.Error(t, err) 93 | require.Nil(t, opened2) 94 | 95 | opened1Other, err := boxed1.OpenWithNonce(sealed1Other, nonce1) 96 | require.NoError(t, err) 97 | require.Equal(t, ref1, opened1Other) 98 | } 99 | 100 | func TestBoxed_DeriveNonce(t *testing.T) { 101 | boxed1, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}) 102 | require.NoError(t, err) 103 | 104 | boxed2, err := NewSecretbox([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3}) 105 | require.NoError(t, err) 106 | 107 | nonce1A, err := boxed1.DeriveNonce([]byte("A")) 108 | require.NotEmpty(t, nonce1A) 109 | require.NoError(t, err) 110 | 111 | nonce1AAgain, err := boxed1.DeriveNonce([]byte("A")) 112 | require.NotEmpty(t, nonce1AAgain) 113 | require.NoError(t, err) 114 | 115 | nonce2A, err := boxed2.DeriveNonce([]byte("A")) 116 | require.NotEmpty(t, nonce2A) 117 | require.NoError(t, err) 118 | 119 | nonce1B, err := boxed1.DeriveNonce([]byte("B")) 120 | require.NotEmpty(t, nonce1B) 121 | require.NoError(t, err) 122 | 123 | require.Equal(t, nonce1A, nonce2A) 124 | require.NotEqual(t, nonce1A, nonce1B) 125 | require.Equal(t, nonce1A, nonce1AAgain) 126 | } 127 | -------------------------------------------------------------------------------- /enc/iface.go: -------------------------------------------------------------------------------- 1 | package enc 2 | 3 | type SharedKey interface { 4 | DeriveNonce(input []byte) ([]byte, error) 5 | 6 | Open(payload []byte) ([]byte, error) 7 | Seal(encrypted []byte) ([]byte, error) 8 | 9 | OpenWithNonce(payload []byte, nonce []byte) ([]byte, error) 10 | SealWithNonce(encrypted []byte, nonce []byte) ([]byte, error) 11 | } 12 | -------------------------------------------------------------------------------- /entry/entry_io.go: -------------------------------------------------------------------------------- 1 | package entry // import "berty.tech/go-ipfs-log/entry" 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ipfs/go-cid" 7 | coreiface "github.com/ipfs/kubo/core/coreiface" 8 | 9 | "berty.tech/go-ipfs-log/iface" 10 | ) 11 | 12 | type FetchOptions = iface.FetchOptions 13 | 14 | // FetchParallel has the same comportement than FetchAll, we keep it for retrop 15 | // compatibility purpose 16 | func FetchParallel(ctx context.Context, ipfs coreiface.CoreAPI, hashes []cid.Cid, options *FetchOptions) []iface.IPFSLogEntry { 17 | fetcher := NewFetcher(ipfs, options) 18 | return fetcher.Fetch(ctx, hashes) 19 | } 20 | 21 | // FetchAll gets entries from their CIDs. 22 | func FetchAll(ctx context.Context, ipfs coreiface.CoreAPI, hashes []cid.Cid, options *FetchOptions) []iface.IPFSLogEntry { 23 | fetcher := NewFetcher(ipfs, options) 24 | return fetcher.Fetch(ctx, hashes) 25 | } 26 | -------------------------------------------------------------------------------- /entry/entry_map.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | import ( 4 | "sync" 5 | 6 | "berty.tech/go-ipfs-log/iface" 7 | ) 8 | 9 | // OrderedMap is an ordered map of entries. 10 | type OrderedMap struct { 11 | lock sync.RWMutex 12 | keys []string 13 | values map[string]iface.IPFSLogEntry 14 | } 15 | 16 | func (o *OrderedMap) Copy() iface.IPFSLogOrderedEntries { 17 | o.lock.RLock() 18 | defer o.lock.RUnlock() 19 | 20 | values := map[string]iface.IPFSLogEntry{} 21 | for k, v := range o.values { 22 | values[k] = v 23 | } 24 | 25 | keys := make([]string, len(o.keys)) 26 | copy(keys, o.keys) 27 | 28 | return &OrderedMap{ 29 | keys: keys, 30 | values: values, 31 | } 32 | } 33 | 34 | func (o *OrderedMap) Reverse() iface.IPFSLogOrderedEntries { 35 | o.lock.Lock() 36 | defer o.lock.Unlock() 37 | 38 | for i := len(o.keys)/2 - 1; i >= 0; i-- { 39 | opp := len(o.keys) - 1 - i 40 | o.keys[i], o.keys[opp] = o.keys[opp], o.keys[i] 41 | } 42 | 43 | return o 44 | } 45 | 46 | // NewOrderedMap creates a new OrderedMap of entries. 47 | func NewOrderedMap() iface.IPFSLogOrderedEntries { 48 | return &OrderedMap{ 49 | lock: sync.RWMutex{}, 50 | values: map[string]iface.IPFSLogEntry{}, 51 | } 52 | } 53 | 54 | // NewOrderedMapFromEntries creates a new OrderedMap of entries from a slice. 55 | func NewOrderedMapFromEntries(entries []iface.IPFSLogEntry) iface.IPFSLogOrderedEntries { 56 | orderedMap := NewOrderedMap() 57 | 58 | for _, e := range entries { 59 | if e == nil || !e.Defined() { 60 | continue 61 | } 62 | 63 | orderedMap.Set(e.GetHash().String(), e) 64 | } 65 | 66 | return orderedMap 67 | } 68 | 69 | // Merge will fusion two OrderedMap of entries. 70 | func (o *OrderedMap) Merge(other iface.IPFSLogOrderedEntries) iface.IPFSLogOrderedEntries { 71 | newMap := NewOrderedMap() 72 | 73 | for _, k := range o.Keys() { 74 | val, _ := o.Get(k) 75 | newMap.Set(k, val) 76 | } 77 | 78 | for _, k := range other.Keys() { 79 | val, _ := other.Get(k) 80 | newMap.Set(k, val) 81 | } 82 | 83 | return newMap 84 | } 85 | 86 | // Get retrieves an Entry using its key. 87 | func (o *OrderedMap) Get(key string) (iface.IPFSLogEntry, bool) { 88 | o.lock.RLock() 89 | defer o.lock.RUnlock() 90 | 91 | val, exists := o.values[key] 92 | return val, exists 93 | } 94 | 95 | // UnsafeGet retrieves an Entry using its key, returns nil if not found. 96 | func (o *OrderedMap) UnsafeGet(key string) iface.IPFSLogEntry { 97 | o.lock.RLock() 98 | defer o.lock.RUnlock() 99 | 100 | val, _ := o.Get(key) 101 | 102 | return val 103 | } 104 | 105 | // Set defines an Entry in the map for a given key. 106 | func (o *OrderedMap) Set(key string, value iface.IPFSLogEntry) { 107 | o.lock.Lock() 108 | defer o.lock.Unlock() 109 | 110 | _, exists := o.values[key] 111 | if !exists { 112 | o.keys = append(o.keys, key) 113 | } 114 | o.values[key] = value 115 | } 116 | 117 | // Slice returns an ordered slice of the values existing in the map. 118 | func (o *OrderedMap) Slice() []iface.IPFSLogEntry { 119 | o.lock.RLock() 120 | defer o.lock.RUnlock() 121 | 122 | keysCount := len(o.keys) 123 | out := make([]iface.IPFSLogEntry, keysCount) 124 | 125 | for i, k := range o.keys { 126 | out[i] = o.UnsafeGet(k) 127 | } 128 | 129 | return out 130 | } 131 | 132 | // Keys retrieves the ordered list of keys in the map. 133 | func (o *OrderedMap) Keys() []string { 134 | o.lock.RLock() 135 | defer o.lock.RUnlock() 136 | 137 | return o.keys 138 | } 139 | 140 | // Len gets the length of the map. 141 | func (o *OrderedMap) Len() int { 142 | o.lock.RLock() 143 | defer o.lock.RUnlock() 144 | 145 | return len(o.keys) 146 | } 147 | 148 | // At gets an item at the given index in the map, returns nil if not found. 149 | func (o *OrderedMap) At(index uint) iface.IPFSLogEntry { 150 | o.lock.RLock() 151 | defer o.lock.RUnlock() 152 | 153 | if uint(len(o.keys)) <= index { 154 | return nil 155 | } 156 | 157 | key := o.keys[index] 158 | 159 | return o.UnsafeGet(key) 160 | } 161 | 162 | var _ iface.IPFSLogOrderedEntries = (*OrderedMap)(nil) 163 | -------------------------------------------------------------------------------- /entry/fetcher.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "berty.tech/go-ipfs-log/identityprovider" 9 | "berty.tech/go-ipfs-log/iface" 10 | "berty.tech/go-ipfs-log/io/cbor" 11 | "github.com/ipfs/go-cid" 12 | coreiface "github.com/ipfs/kubo/core/coreiface" 13 | "golang.org/x/sync/semaphore" 14 | ) 15 | 16 | type taskKind int 17 | 18 | const ( 19 | taskKindAdded = iota 20 | taskKindInProgress 21 | taskKindDone 22 | ) 23 | 24 | func noopShouldExclude(_ cid.Cid) bool { 25 | return false 26 | } 27 | 28 | type Fetcher struct { 29 | length int 30 | maxClock int // keep track of the latest clock time during load 31 | minClock int // keep track of the minimum clock time during load 32 | muClock sync.Mutex 33 | 34 | timeout time.Duration 35 | io iface.IO 36 | provider identityprovider.Interface 37 | shouldExclude iface.ExcludeFunc 38 | tasksCache map[cid.Cid]taskKind 39 | condProcess *sync.Cond 40 | muProcess *sync.RWMutex 41 | sem *semaphore.Weighted 42 | ipfs coreiface.CoreAPI 43 | progressChan chan iface.IPFSLogEntry 44 | } 45 | 46 | func NewFetcher(ipfs coreiface.CoreAPI, options *FetchOptions) *Fetcher { 47 | // set default 48 | length := -1 49 | if options.Length != nil { 50 | length = *options.Length 51 | } 52 | 53 | if options.Concurrency <= 0 { 54 | options.Concurrency = 32 55 | } 56 | 57 | if options.IO == nil { 58 | io, err := cbor.IO(&Entry{}, &LamportClock{}) 59 | if err != nil { 60 | return nil 61 | } 62 | 63 | options.IO = io 64 | } 65 | 66 | if options.ShouldExclude == nil { 67 | options.ShouldExclude = noopShouldExclude 68 | } 69 | 70 | muProcess := sync.RWMutex{} 71 | 72 | // create Fetcher 73 | return &Fetcher{ 74 | io: options.IO, 75 | length: length, 76 | timeout: options.Timeout, 77 | shouldExclude: options.ShouldExclude, 78 | sem: semaphore.NewWeighted(int64(options.Concurrency)), 79 | ipfs: ipfs, 80 | progressChan: options.ProgressChan, 81 | muProcess: &muProcess, 82 | condProcess: sync.NewCond(&muProcess), 83 | maxClock: 0, 84 | minClock: 0, 85 | tasksCache: make(map[cid.Cid]taskKind), 86 | } 87 | } 88 | 89 | func (f *Fetcher) Fetch(ctx context.Context, hashes []cid.Cid) []iface.IPFSLogEntry { 90 | if f.timeout > 0 { 91 | var cancel context.CancelFunc 92 | ctx, cancel = context.WithTimeout(ctx, f.timeout) 93 | defer cancel() 94 | } 95 | 96 | return f.processQueue(ctx, hashes) 97 | } 98 | 99 | func (f *Fetcher) processQueue(ctx context.Context, hashes []cid.Cid) []iface.IPFSLogEntry { 100 | results := []iface.IPFSLogEntry{} 101 | queue := newProcessQueue() 102 | 103 | f.muProcess.Lock() 104 | f.addHashesToQueue(queue, hashes...) 105 | taskInProgress := 0 106 | for queue.Len() > 0 { 107 | // acquire a process slot limited by concurrency limit 108 | if err := f.acquireProcessSlot(ctx); err != nil { 109 | // @FIXME(gfanton): log this 110 | // fmt.Printf("error while process next: %s\n", err.Error()) 111 | break 112 | } 113 | 114 | // get next hash 115 | hash := queue.Next() 116 | f.tasksCache[hash] = taskKindInProgress 117 | 118 | // run process 119 | go func(hash cid.Cid) { 120 | entry, _ := f.fetchEntry(ctx, hash) 121 | // if err != nil { 122 | // @FIXME(gfanton): log this 123 | // fmt.Printf("unable to fetch entry: %s\n", err.Error()) 124 | // } 125 | 126 | // free process slot 127 | f.processDone() 128 | 129 | f.muProcess.Lock() 130 | 131 | if entry != nil { 132 | entryHash := entry.GetHash() 133 | var lastEntry iface.IPFSLogEntry 134 | if len(results) > 0 { 135 | lastEntry = results[len(results)-1] 136 | } 137 | 138 | // update clock 139 | f.updateClock(ctx, entry, lastEntry) 140 | 141 | // if we don't know this hash yet, add it to result 142 | cache := f.tasksCache[entryHash] 143 | if cache == taskKindAdded || cache == taskKindInProgress { 144 | ts := entry.GetClock().GetTime() 145 | isLater := len(results) >= f.length && ts >= f.minClock 146 | if f.length < 0 || len(results) < f.length || isLater { 147 | results = append(results, entry) 148 | // signal progress 149 | if f.progressChan != nil { 150 | f.progressChan <- entry 151 | } 152 | } 153 | 154 | f.tasksCache[entryHash] = taskKindDone 155 | 156 | // add next elems to queue 157 | f.addNextEntry(ctx, queue, entry, results) 158 | } 159 | } 160 | 161 | // mark this process as done 162 | taskInProgress-- 163 | 164 | // signal that a slot is available 165 | f.condProcess.Signal() 166 | 167 | f.muProcess.Unlock() 168 | }(hash) 169 | 170 | // increase in progress task counter 171 | taskInProgress++ 172 | 173 | // wait until a task is added or that no running task is in progress 174 | for queue.Len() == 0 && taskInProgress > 0 { 175 | f.condProcess.Wait() 176 | } 177 | } 178 | 179 | // wait until all process are done/canceled 180 | for taskInProgress > 0 { 181 | f.condProcess.Wait() 182 | } 183 | 184 | f.muProcess.Unlock() 185 | 186 | return results 187 | } 188 | 189 | func (f *Fetcher) updateClock(_ context.Context, entry, lastEntry iface.IPFSLogEntry) { 190 | f.muClock.Lock() 191 | 192 | ts := entry.GetClock().GetTime() 193 | 194 | // Update min/max clocks 195 | if f.maxClock < ts { 196 | f.maxClock = ts 197 | } 198 | 199 | if lastEntry != nil { 200 | if ts := lastEntry.GetClock().GetTime(); ts < f.minClock { 201 | f.minClock = ts 202 | } 203 | } else { 204 | f.minClock = f.maxClock 205 | } 206 | 207 | f.muClock.Unlock() 208 | } 209 | 210 | func (f *Fetcher) exclude(hash cid.Cid) (yes bool) { 211 | if yes = !hash.Defined(); yes { 212 | return 213 | } 214 | 215 | // do we have it in the internal cache ? 216 | if _, yes = f.tasksCache[hash]; yes { 217 | return 218 | } 219 | 220 | // should the caller want it ? 221 | yes = f.shouldExclude(hash) 222 | return 223 | } 224 | 225 | func (f *Fetcher) addNextEntry(_ context.Context, queue processQueue, entry iface.IPFSLogEntry, results []iface.IPFSLogEntry) { 226 | ts := entry.GetClock().GetTime() 227 | 228 | if f.length < 0 { 229 | // If we're fetching all entries (length === -1), adds nexts and refs to the queue 230 | f.addHashesToQueue(queue, entry.GetNext()...) 231 | f.addHashesToQueue(queue, entry.GetRefs()...) 232 | return 233 | } 234 | 235 | // If we're fetching entries up to certain length, 236 | // fetch the next if result is filled up, to make sure we "check" 237 | // the next entry if its clock is later than what we have in the result 238 | if len(results) < f.length || ts > f.minClock || ts == f.minClock { 239 | for _, h := range entry.GetNext() { 240 | f.addHashToQueue(queue, f.maxClock-ts, h) 241 | } 242 | } 243 | if len(results)+len(entry.GetRefs()) <= f.length { 244 | for i, h := range entry.GetRefs() { 245 | f.addHashToQueue(queue, f.maxClock-ts+((i+1)*i), h) 246 | } 247 | } 248 | } 249 | 250 | func (f *Fetcher) fetchEntry(ctx context.Context, hash cid.Cid) (entry iface.IPFSLogEntry, err error) { 251 | // Load the entry 252 | return FromMultihashWithIO(ctx, f.ipfs, hash, f.provider, f.io) 253 | } 254 | 255 | func (f *Fetcher) addHashesToQueue(queue processQueue, hashes ...cid.Cid) (added int) { 256 | for i, h := range hashes { 257 | added += f.addHashToQueue(queue, i, h) 258 | } 259 | 260 | return 261 | } 262 | 263 | func (f *Fetcher) addHashToQueue(queue processQueue, index int, hash cid.Cid) (added int) { 264 | if f.exclude(hash) { 265 | return 266 | } 267 | 268 | queue.Add(index, hash) 269 | f.tasksCache[hash] = taskKindAdded 270 | 271 | return 1 272 | 273 | } 274 | 275 | func (f *Fetcher) acquireProcessSlot(ctx context.Context) error { 276 | return f.sem.Acquire(ctx, 1) 277 | } 278 | 279 | func (f *Fetcher) processDone() { 280 | // signal that a process slot is available 281 | f.sem.Release(1) 282 | } 283 | -------------------------------------------------------------------------------- /entry/lamportclock.go: -------------------------------------------------------------------------------- 1 | package entry // import "berty.tech/go-ipfs-log/entry" 2 | 3 | import ( 4 | "bytes" 5 | "math" 6 | 7 | "berty.tech/go-ipfs-log/iface" 8 | ) 9 | 10 | type LamportClock struct { 11 | ID []byte `json:"id,omitempty"` 12 | Time int `json:"time,omitempty"` 13 | } 14 | 15 | func (l *LamportClock) Defined() bool { 16 | return l != nil && len(l.ID) > 0 17 | } 18 | 19 | func (l *LamportClock) New() iface.IPFSLogLamportClock { 20 | return &LamportClock{} 21 | } 22 | 23 | func (l *LamportClock) SetID(i []byte) { 24 | l.ID = i 25 | } 26 | 27 | func (l *LamportClock) SetTime(i int) { 28 | l.Time = i 29 | } 30 | 31 | func (l *LamportClock) GetID() []byte { 32 | return l.ID 33 | } 34 | 35 | func (l *LamportClock) GetTime() int { 36 | return l.Time 37 | } 38 | 39 | // Tick increments the time value, returns a new instance of LamportClock. 40 | func (l *LamportClock) Tick() iface.IPFSLogLamportClock { 41 | l.Time++ 42 | 43 | return &LamportClock{ 44 | ID: l.ID, 45 | Time: l.Time, 46 | } 47 | } 48 | 49 | // Merge fusion two LamportClocks. 50 | func (l *LamportClock) Merge(clock iface.IPFSLogLamportClock) iface.IPFSLogLamportClock { 51 | l.Time = int(math.Max(float64(l.Time), float64(clock.GetTime()))) 52 | 53 | return &LamportClock{ 54 | ID: l.ID, 55 | Time: l.Time, 56 | } 57 | } 58 | 59 | // Compare calculate the "distance" based on the clock, ie. lower or greater. 60 | func (l *LamportClock) Compare(b iface.IPFSLogLamportClock) int { 61 | // TODO: Make it a Golang slice-compatible sort function 62 | dist := l.Time - b.GetTime() 63 | 64 | // If the sequence number is the same (concurrent events), 65 | // return the comparison between IDs 66 | if dist == 0 { 67 | return bytes.Compare(l.ID, b.GetID()) 68 | } 69 | 70 | return dist 71 | } 72 | 73 | // CopyLamportClock returns a copy of a lamport clock 74 | func CopyLamportClock(clock iface.IPFSLogLamportClock) *LamportClock { 75 | return NewLamportClock(clock.GetID(), clock.GetTime()) 76 | } 77 | 78 | // NewLamportClock creates a new LamportClock instance. 79 | func NewLamportClock(identity []byte, time int) *LamportClock { 80 | return &LamportClock{ 81 | ID: identity, 82 | Time: time, 83 | } 84 | } 85 | 86 | var _ iface.IPFSLogLamportClock = (*LamportClock)(nil) 87 | -------------------------------------------------------------------------------- /entry/queue.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | import ( 4 | "container/heap" 5 | 6 | cid "github.com/ipfs/go-cid" 7 | ) 8 | 9 | type processQueue interface { 10 | Add(index int, hash cid.Cid) 11 | Next() cid.Cid 12 | Len() int 13 | } 14 | 15 | func newProcessQueue() processQueue { 16 | var items priorityQueue = []*item{} 17 | heap.Init(&items) 18 | return &items 19 | } 20 | 21 | type item struct { 22 | hash cid.Cid 23 | index int 24 | } 25 | 26 | // processHashQueue is not thread safe 27 | type priorityQueue []*item 28 | 29 | func (pq *priorityQueue) Add(index int, hash cid.Cid) { 30 | heap.Push(pq, &item{ 31 | hash: hash, 32 | index: index, 33 | }) 34 | // *pq = append(*pq, hash...) 35 | } 36 | 37 | func (pq *priorityQueue) Next() (hash cid.Cid) { 38 | item := heap.Pop(pq).(*item) 39 | return item.hash 40 | } 41 | 42 | func (pq *priorityQueue) Push(x interface{}) { 43 | item := x.(*item) 44 | *pq = append(*pq, item) 45 | } 46 | 47 | func (pq *priorityQueue) Pop() interface{} { 48 | old := *pq 49 | n := len(old) 50 | item := old[n-1] 51 | old[n-1] = nil // avoid memory leak 52 | *pq = old[0 : n-1] 53 | return item 54 | } 55 | 56 | func (pq priorityQueue) Len() int { return len(pq) } 57 | 58 | func (pq priorityQueue) Less(i, j int) bool { 59 | // We want Pop to give us the highest, not lowest, priority so we use greater than here. 60 | return pq[i].index < pq[j].index 61 | } 62 | 63 | func (pq priorityQueue) Swap(i, j int) { 64 | pq[i], pq[j] = pq[j], pq[i] 65 | } 66 | 67 | // func (pq processHashQueue) GetQueue() []processItem { 68 | // return pq 69 | // } 70 | -------------------------------------------------------------------------------- /entry/sorting/sorting.go: -------------------------------------------------------------------------------- 1 | // Package sorting includes utilities for ordering slices of Entries. 2 | package sorting 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "sort" 8 | "strings" 9 | 10 | "berty.tech/go-ipfs-log/errmsg" 11 | "berty.tech/go-ipfs-log/iface" 12 | ) 13 | 14 | func SortByClocks(a, b iface.IPFSLogEntry, resolveConflict func(a iface.IPFSLogEntry, b iface.IPFSLogEntry) (int, error)) (int, error) { 15 | diff := a.GetClock().Compare(b.GetClock()) 16 | 17 | if diff == 0 { 18 | return resolveConflict(a, b) 19 | } 20 | 21 | return diff, nil 22 | } 23 | 24 | func SortByClockID(a, b iface.IPFSLogEntry, resolveConflict func(a iface.IPFSLogEntry, b iface.IPFSLogEntry) (int, error)) (int, error) { 25 | comparedIDs := bytes.Compare(a.GetClock().GetID(), b.GetClock().GetID()) 26 | 27 | if comparedIDs == 0 { 28 | return resolveConflict(a, b) 29 | } 30 | 31 | return comparedIDs, nil 32 | } 33 | 34 | func First(_, _ iface.IPFSLogEntry) (int, error) { 35 | return 1, nil 36 | } 37 | 38 | func FirstWriteWins(a, b iface.IPFSLogEntry) (int, error) { 39 | res, err := LastWriteWins(a, b) 40 | 41 | if err != nil { 42 | return 0, errmsg.ErrTiebreakerFailed.Wrap(err) 43 | } 44 | 45 | return res * -1, nil 46 | } 47 | 48 | func LastWriteWins(a, b iface.IPFSLogEntry) (int, error) { 49 | sortByID := func(a, b iface.IPFSLogEntry) (int, error) { 50 | return SortByClockID(a, b, First) 51 | } 52 | 53 | sortByEntryClocks := func(a, b iface.IPFSLogEntry) (int, error) { 54 | return SortByClocks(a, b, sortByID) 55 | } 56 | 57 | return sortByEntryClocks(a, b) 58 | } 59 | 60 | func SortByEntryHash(a, b iface.IPFSLogEntry) (int, error) { 61 | // Ultimate conflict resolution (compare hashes) 62 | compareHash := func(a, b iface.IPFSLogEntry) (int, error) { 63 | return strings.Compare(a.GetHash().String(), b.GetHash().String()), nil 64 | } 65 | 66 | // Sort two entries by their clock id, if the same then compare hashes 67 | sortByID := func(a, b iface.IPFSLogEntry) (int, error) { 68 | return SortByClockID(a, b, compareHash) 69 | } 70 | 71 | // Sort two entries by their clock time, if concurrent, 72 | // determine sorting using provided conflict resolution function 73 | // Sort entries by clock time as the primary sort criteria 74 | return SortByClocks(a, b, sortByID) 75 | } 76 | 77 | func NoZeroes(compFunc func(a, b iface.IPFSLogEntry) (int, error)) func(a, b iface.IPFSLogEntry) (int, error) { 78 | return func(a, b iface.IPFSLogEntry) (int, error) { 79 | ret, err := compFunc(a, b) 80 | if err != nil { 81 | return 0, errmsg.ErrTiebreakerFailed.Wrap(err) 82 | } 83 | 84 | if ret != 0 { 85 | return ret, nil 86 | } 87 | 88 | return 0, errmsg.ErrTiebreakerBogus 89 | } 90 | } 91 | 92 | func Reverse(a []iface.IPFSLogEntry) { 93 | for i := len(a)/2 - 1; i >= 0; i-- { 94 | opp := len(a) - 1 - i 95 | a[i], a[opp] = a[opp], a[i] 96 | } 97 | } 98 | 99 | func Compare(a, b iface.IPFSLogEntry) (int, error) { 100 | // TODO: Make it a Golang slice-compatible sort function 101 | if a == nil || b == nil || !a.Defined() || !b.Defined() { 102 | return 0, errmsg.ErrEntryNotDefined 103 | } 104 | 105 | return a.GetClock().Compare(b.GetClock()), nil 106 | } 107 | 108 | func Sort(compFunc func(a, b iface.IPFSLogEntry) (int, error), values []iface.IPFSLogEntry, reverse bool) { 109 | if reverse { 110 | sort.SliceStable(values, func(i, j int) bool { 111 | ret, err := compFunc(values[i], values[j]) 112 | if err != nil { 113 | fmt.Printf("error while comparing: %v\n", err) 114 | return false 115 | } 116 | return ret > 0 117 | }) 118 | } else { 119 | sort.SliceStable(values, func(i, j int) bool { 120 | ret, err := compFunc(values[i], values[j]) 121 | if err != nil { 122 | fmt.Printf("error while comparing: %v\n", err) 123 | return false 124 | } 125 | return ret < 0 126 | }) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /entry/utils.go: -------------------------------------------------------------------------------- 1 | package entry // import "berty.tech/go-ipfs-log/entry" 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | 7 | "berty.tech/go-ipfs-log/iface" 8 | ) 9 | 10 | // Difference gets the list of values not present in both entries sets. 11 | func Difference(a []iface.IPFSLogEntry, b []iface.IPFSLogEntry) []iface.IPFSLogEntry { 12 | existing := map[string]bool{} 13 | processed := map[string]bool{} 14 | var diff []iface.IPFSLogEntry 15 | 16 | for _, v := range a { 17 | existing[v.GetHash().String()] = true 18 | } 19 | 20 | for _, v := range b { 21 | isInFirst := existing[v.GetHash().String()] 22 | hasBeenProcessed := processed[v.GetHash().String()] 23 | if !isInFirst && !hasBeenProcessed { 24 | diff = append(diff, v) 25 | processed[v.GetHash().String()] = true 26 | } 27 | } 28 | 29 | return diff 30 | } 31 | 32 | //func FindTails(entries []*Entry) []*Entry { 33 | // // Reverse index { next -> entry } 34 | // reverseIndex := map[string][]*Entry{} 35 | // // Null index containing entries that have no parents (nexts) 36 | // nullIndex := []*Entry{} 37 | // // Hashes for all entries for quick lookups 38 | // hashes := map[string]bool{} 39 | // // Hashes of all next entries 40 | // nexts := []cid.Cid{} 41 | // 42 | // for _, e := range entries { 43 | // if len(e.Next) == 0 { 44 | // nullIndex = append(nullIndex, e) 45 | // } 46 | // 47 | // for _, nextE := range e.Next { 48 | // reverseIndex[nextE.String()] = append(reverseIndex[nextE.String()], e) 49 | // } 50 | // 51 | // nexts = append(nexts, e.Next...) 52 | // 53 | // hashes[e.Hash.String()] = true 54 | // } 55 | // 56 | // tails := []*Entry{} 57 | // 58 | // for _, n := range nexts { 59 | // if _, ok := hashes[n.String()]; !ok { 60 | // continue 61 | // } 62 | // 63 | // tails = append(tails, reverseIndex[n.String()]...) 64 | // } 65 | // 66 | // tails = append(tails, nullIndex...) 67 | // 68 | // return NewOrderedMapFromEntries(tails).Slice() 69 | //} 70 | // 71 | //func FindTailHashes(entries []*Entry) []string { 72 | // res := []string{} 73 | // hashes := map[string]bool{} 74 | // for _, e := range entries { 75 | // hashes[e.Hash.String()] = true 76 | // } 77 | // 78 | // for _, e := range entries { 79 | // nextLength := len(e.Next) 80 | // 81 | // for i := range e.Next { 82 | // next := e.Next[nextLength-i] 83 | // if _, ok := hashes[next.String()]; !ok { 84 | // res = append([]string{e.Hash.String()}, res...) 85 | // } 86 | // } 87 | // } 88 | // 89 | // return res 90 | //} 91 | 92 | // FindHeads search entries heads in an OrderedMap. 93 | func FindHeads(entries iface.IPFSLogOrderedEntries) []iface.IPFSLogEntry { 94 | if entries == nil { 95 | return nil 96 | } 97 | 98 | var result []iface.IPFSLogEntry 99 | items := map[string]string{} 100 | 101 | for _, k := range entries.Keys() { 102 | e := entries.UnsafeGet(k) 103 | for _, n := range e.GetNext() { 104 | items[n.String()] = e.GetHash().String() 105 | } 106 | } 107 | 108 | for _, h := range entries.Keys() { 109 | e, ok := items[h] 110 | if ok || e != "" { 111 | continue 112 | } 113 | 114 | result = append(result, entries.UnsafeGet(h)) 115 | } 116 | 117 | sort.SliceStable(result, func(a, b int) bool { 118 | return bytes.Compare(result[a].GetClock().GetID(), result[b].GetClock().GetID()) < 0 119 | }) 120 | 121 | return result 122 | } 123 | -------------------------------------------------------------------------------- /errmsg/errmsg.go: -------------------------------------------------------------------------------- 1 | // Package errmsg defines error messages used by the Go version of IPFS Log. 2 | package errmsg // import "berty.tech/go-ipfs-log/errmsg" 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | // https://dave.cheney.net/2016/04/07/constant-errors 9 | 10 | type Error string 11 | 12 | func (e Error) Error() string { return string(e) } 13 | 14 | func (e Error) Wrap(inner error) error { return fmt.Errorf("%s: %w", e, inner) } 15 | 16 | const ( 17 | ErrCBOROperationFailed = Error("CBOR operation failed") 18 | ErrCIDSerializationFailed = Error("CID deserialization failed") 19 | ErrClockDeserialization = Error("unable to deserialize clock") 20 | ErrEmptyLogSerialization = Error("can't serialize an empty log") 21 | ErrEntriesNotDefined = Error("entries not defined") 22 | ErrEntryDeserializationFailed = Error("entry deserialization failed") 23 | ErrEntryNotDefined = Error("entry is not defined") 24 | ErrEntryNotHashable = Error("entry is hashable") 25 | ErrFetchOptionsNotDefined = Error("fetch options not defined") 26 | ErrFilterLTENotFound = Error("entry specified at LTE not found") 27 | ErrFilterLTNotFound = Error("entry specified at LT not found") 28 | ErrIPFSNotDefined = Error("ipfs instance not defined") 29 | ErrIPFSOperationFailed = Error("IPFS operation failed") 30 | ErrIdentityCreationFailed = Error("identity creation failed") 31 | ErrIdentityDeserialization = Error("unable to deserialize identity") 32 | ErrIdentityNotDefined = Error("identity not defined") 33 | ErrIdentityProviderNotDefined = Error("an identity provider constructor needs to be given as an option") 34 | ErrIdentityProviderNotSupported = Error("identity provider is not supported") 35 | ErrIdentitySigDeserialization = Error("identity signature deserialization failed") 36 | ErrIdentityUnknown = Error("unknown identity used") 37 | ErrInvalidPrivKeyFormat = Error("unable to unmarshal private key") 38 | ErrInvalidPubKeyFormat = Error("unable to unmarshal public key") 39 | ErrIteratorOptionsNotDefined = Error("no iterator options specified") 40 | ErrJSONSerializationFailed = Error("JSON serialization failed") 41 | ErrKeyDeserialization = Error("unable to deserialize key") 42 | ErrKeyGenerationFailed = Error("key generation failed") 43 | ErrKeyNotDefined = Error("key is not defined") 44 | ErrKeyNotInKeystore = Error("private signing key not found from Keystore") 45 | ErrKeyStoreCreateEntry = Error("unable to create key store entry") 46 | ErrKeyStoreInitFailed = Error("keystore initialization failed") 47 | ErrKeyStorePutFailed = Error("keystore put failed") 48 | ErrKeystoreNotDefined = Error("keystore not defined") 49 | ErrLogAppendDenied = Error("log append denied") 50 | ErrLogAppendFailed = Error("log append failed") 51 | ErrLogFromEntry = Error("new from entry failed") 52 | ErrLogFromEntryHash = Error("new from multi hash failed") 53 | ErrLogFromJSON = Error("new from JSON failed") 54 | ErrLogFromMultiHash = Error("new from entry hash failed") 55 | ErrLogIDNotDefined = Error("log ID not defined") 56 | ErrLogJoinFailed = Error("log join failed") 57 | ErrLogJoinNotDefined = Error("log to join not defined") 58 | ErrLogOptionsNotDefined = Error("log options not defined") 59 | ErrLogTraverseFailed = Error("log traverse failed") 60 | ErrMultibaseOperationFailed = Error("Multibase operation failed") 61 | ErrNotSecp256k1PubKey = Error("supplied key is not a valid Secp256k1 public key") 62 | ErrOutputChannelNotDefined = Error("no output channel specified") 63 | ErrPayloadNotDefined = Error("payload not defined") 64 | ErrPubKeyDeserialization = Error("public key deserialization failed") 65 | ErrPubKeySerialization = Error("unable to serialize public key") 66 | ErrSigDeserialization = Error("unable to deserialize signature") 67 | ErrSigNotDefined = Error("signature is not defined") 68 | ErrSigNotVerified = Error("signature could not verified") 69 | ErrSigSign = Error("unable to sign value") 70 | ErrTiebreakerBogus = Error("log's tiebreaker function has returned zero and therefore cannot be") 71 | ErrTiebreakerFailed = Error("tiebreaker failed") 72 | ErrIPFSWriteFailed = Error("ipfs write failed") 73 | ErrIPFSReadFailed = Error("ipfs read failed") 74 | ErrIPFSReadUnmarshalFailed = Error("ipfs unmarshal failed") 75 | ErrPBReadUnmarshalFailed = Error("protobuf unmarshal failed") 76 | ErrEncrypt = Error("encryption error") 77 | ErrDecrypt = Error("decryption error") 78 | ) 79 | -------------------------------------------------------------------------------- /example/doc.go: -------------------------------------------------------------------------------- 1 | package example // import "berty.tech/go-ipfs-log/example" 2 | -------------------------------------------------------------------------------- /example/example_log_append_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | 8 | idp "berty.tech/go-ipfs-log/identityprovider" 9 | "berty.tech/go-ipfs-log/keystore" 10 | "github.com/ipfs/go-datastore" 11 | dssync "github.com/ipfs/go-datastore/sync" 12 | config "github.com/ipfs/kubo/config" 13 | ipfs_core "github.com/ipfs/kubo/core" 14 | "github.com/ipfs/kubo/core/coreapi" 15 | ipfs_libp2p "github.com/ipfs/kubo/core/node/libp2p" 16 | ipfs_repo "github.com/ipfs/kubo/repo" 17 | "github.com/libp2p/go-libp2p" 18 | "github.com/libp2p/go-libp2p/core/host" 19 | "github.com/libp2p/go-libp2p/core/peer" 20 | pstore "github.com/libp2p/go-libp2p/core/peerstore" 21 | 22 | log "berty.tech/go-ipfs-log" 23 | ) 24 | 25 | func buildHostOverrideExample(id peer.ID, ps pstore.Peerstore, options ...libp2p.Option) (host.Host, error) { 26 | return ipfs_libp2p.DefaultHostOption(id, ps, options...) 27 | } 28 | 29 | func newRepo() (ipfs_repo.Repo, error) { 30 | // Generating config 31 | cfg, err := config.Init(ioutil.Discard, 2048) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | // Listen on local interface only 37 | cfg.Addresses.Swarm = []string{ 38 | "/ip4/127.0.0.1/tcp/0", 39 | } 40 | // we don't need ressources manager for test 41 | cfg.Swarm.ResourceMgr.Enabled = config.False 42 | 43 | // Do not bootstrap on ipfs node 44 | cfg.Bootstrap = []string{} 45 | 46 | return &ipfs_repo.Mock{ 47 | D: dssync.MutexWrap(datastore.NewMapDatastore()), 48 | C: *cfg, 49 | }, nil 50 | } 51 | 52 | func buildNode(ctx context.Context) (*ipfs_core.IpfsNode, error) { 53 | r, err := newRepo() 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | cfg := &ipfs_core.BuildCfg{ 59 | Online: true, 60 | Repo: r, 61 | Host: buildHostOverrideExample, 62 | } 63 | 64 | return ipfs_core.NewNode(ctx, cfg) 65 | } 66 | 67 | func Example_logAppend() { 68 | ctx := context.Background() 69 | 70 | // Build Ipfs Node A 71 | nodeA, err := buildNode(ctx) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | // Build Ipfs Node B 77 | nodeB, err := buildNode(ctx) 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | nodeBInfo := peer.AddrInfo{ 83 | ID: nodeB.Identity, 84 | Addrs: nodeB.PeerHost.Addrs(), 85 | } 86 | 87 | // Connecting NodeA with NodeB 88 | if err := nodeA.PeerHost.Connect(ctx, nodeBInfo); err != nil { 89 | panic(fmt.Errorf("connect error: %s", err)) 90 | } 91 | 92 | serviceA, err := coreapi.NewCoreAPI(nodeA) 93 | if err != nil { 94 | panic(fmt.Errorf("coreapi error: %s", err)) 95 | } 96 | 97 | serviceB, err := coreapi.NewCoreAPI(nodeB) 98 | if err != nil { 99 | panic(fmt.Errorf("coreapi error: %s", err)) 100 | } 101 | 102 | // Fill up datastore with identities 103 | ds := dssync.MutexWrap(datastore.NewMapDatastore()) 104 | ks, err := keystore.NewKeystore(ds) 105 | if err != nil { 106 | panic(err) 107 | } 108 | 109 | // Create identity A 110 | identityA, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 111 | Keystore: ks, 112 | ID: "userA", 113 | Type: "orbitdb", 114 | }) 115 | 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | // Create identity B 121 | identityB, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 122 | Keystore: ks, 123 | ID: "userB", 124 | Type: "orbitdb", 125 | }) 126 | 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | // creating log 132 | logA, err := log.NewLog(serviceA, identityA, &log.LogOptions{ID: "A"}) 133 | if err != nil { 134 | panic(err) 135 | } 136 | 137 | // nodeA Append data (hello world)" 138 | _, err = logA.Append(ctx, []byte("hello world"), nil) 139 | if err != nil { 140 | panic(fmt.Errorf("append error: %s", err)) 141 | } 142 | 143 | h, err := logA.ToMultihash(ctx) 144 | if err != nil { 145 | panic(fmt.Errorf("ToMultihash error: %s", err)) 146 | } 147 | 148 | res, err := log.NewFromMultihash(ctx, serviceB, identityB, h, &log.LogOptions{}, &log.FetchOptions{}) 149 | if err != nil { 150 | panic(fmt.Errorf("NewFromMultihash error: %s", err)) 151 | } 152 | 153 | // nodeB lookup logA 154 | fmt.Println(res.ToString(nil)) 155 | 156 | // Output: 157 | // hello world 158 | } 159 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module berty.tech/go-ipfs-log/example 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.8 6 | 7 | require ( 8 | berty.tech/go-ipfs-log v0.0.0 9 | github.com/ipfs/go-datastore v0.6.0 10 | github.com/ipfs/kubo v0.27.0 11 | github.com/libp2p/go-libp2p v0.33.0 12 | ) 13 | 14 | require ( 15 | bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc // indirect 16 | github.com/Jorropo/jsync v1.0.1 // indirect 17 | github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect 18 | github.com/benbjohnson/clock v1.3.5 // indirect 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/blang/semver/v4 v4.0.0 // indirect 21 | github.com/btcsuite/btcd v0.22.1 // indirect 22 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 23 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 24 | github.com/containerd/cgroups v1.1.0 // indirect 25 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 26 | github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 // indirect 27 | github.com/cskr/pubsub v1.0.2 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect 30 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 31 | github.com/docker/go-units v0.5.0 // indirect 32 | github.com/dustin/go-humanize v1.0.1 // indirect 33 | github.com/elastic/gosigar v0.14.2 // indirect 34 | github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect 35 | github.com/flynn/noise v1.1.0 // indirect 36 | github.com/francoispqt/gojay v1.2.13 // indirect 37 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 38 | github.com/go-logr/logr v1.4.1 // indirect 39 | github.com/go-logr/stdr v1.2.2 // indirect 40 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 41 | github.com/godbus/dbus/v5 v5.1.0 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/golang/protobuf v1.5.3 // indirect 44 | github.com/google/gopacket v1.1.19 // indirect 45 | github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect 46 | github.com/google/uuid v1.5.0 // indirect 47 | github.com/gorilla/websocket v1.5.1 // indirect 48 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect 49 | github.com/hashicorp/errwrap v1.1.0 // indirect 50 | github.com/hashicorp/go-multierror v1.1.1 // indirect 51 | github.com/hashicorp/golang-lru v1.0.2 // indirect 52 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 53 | github.com/huin/goupnp v1.3.0 // indirect 54 | github.com/ipfs/bbloom v0.0.4 // indirect 55 | github.com/ipfs/boxo v0.18.0 // indirect 56 | github.com/ipfs/go-bitfield v1.1.0 // indirect 57 | github.com/ipfs/go-block-format v0.2.0 // indirect 58 | github.com/ipfs/go-cid v0.4.1 // indirect 59 | github.com/ipfs/go-cidutil v0.1.0 // indirect 60 | github.com/ipfs/go-ds-measure v0.2.0 // indirect 61 | github.com/ipfs/go-fs-lock v0.0.7 // indirect 62 | github.com/ipfs/go-ipfs-delay v0.0.1 // indirect 63 | github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect 64 | github.com/ipfs/go-ipfs-pq v0.0.3 // indirect 65 | github.com/ipfs/go-ipfs-redirects-file v0.1.1 // indirect 66 | github.com/ipfs/go-ipfs-util v0.0.3 // indirect 67 | github.com/ipfs/go-ipld-cbor v0.1.0 // indirect 68 | github.com/ipfs/go-ipld-format v0.6.0 // indirect 69 | github.com/ipfs/go-ipld-legacy v0.2.1 // indirect 70 | github.com/ipfs/go-log v1.0.5 // indirect 71 | github.com/ipfs/go-log/v2 v2.5.1 // indirect 72 | github.com/ipfs/go-metrics-interface v0.0.1 // indirect 73 | github.com/ipfs/go-peertaskqueue v0.8.1 // indirect 74 | github.com/ipfs/go-unixfsnode v1.9.0 // indirect 75 | github.com/ipld/go-car/v2 v2.13.1 // indirect 76 | github.com/ipld/go-codec-dagpb v1.6.0 // indirect 77 | github.com/ipld/go-ipld-prime v0.21.0 // indirect 78 | github.com/jackpal/go-nat-pmp v1.0.2 // indirect 79 | github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect 80 | github.com/jbenet/goprocess v0.1.4 // indirect 81 | github.com/klauspost/compress v1.17.6 // indirect 82 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 83 | github.com/koron/go-ssdp v0.0.4 // indirect 84 | github.com/libp2p/go-buffer-pool v0.1.0 // indirect 85 | github.com/libp2p/go-cidranger v1.1.0 // indirect 86 | github.com/libp2p/go-doh-resolver v0.4.0 // indirect 87 | github.com/libp2p/go-flow-metrics v0.1.0 // indirect 88 | github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect 89 | github.com/libp2p/go-libp2p-kad-dht v0.24.4 // indirect 90 | github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect 91 | github.com/libp2p/go-libp2p-pubsub v0.10.0 // indirect 92 | github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect 93 | github.com/libp2p/go-libp2p-record v0.2.0 // indirect 94 | github.com/libp2p/go-libp2p-routing-helpers v0.7.3 // indirect 95 | github.com/libp2p/go-libp2p-xor v0.1.0 // indirect 96 | github.com/libp2p/go-msgio v0.3.0 // indirect 97 | github.com/libp2p/go-nat v0.2.0 // indirect 98 | github.com/libp2p/go-netroute v0.2.1 // indirect 99 | github.com/libp2p/go-reuseport v0.4.0 // indirect 100 | github.com/libp2p/go-yamux/v4 v4.0.1 // indirect 101 | github.com/libp2p/zeroconf/v2 v2.2.0 // indirect 102 | github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect 103 | github.com/mattn/go-isatty v0.0.20 // indirect 104 | github.com/miekg/dns v1.1.58 // indirect 105 | github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect 106 | github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect 107 | github.com/minio/sha256-simd v1.0.1 // indirect 108 | github.com/mitchellh/go-homedir v1.1.0 // indirect 109 | github.com/mr-tron/base58 v1.2.0 // indirect 110 | github.com/multiformats/go-base32 v0.1.0 // indirect 111 | github.com/multiformats/go-base36 v0.2.0 // indirect 112 | github.com/multiformats/go-multiaddr v0.12.2 // indirect 113 | github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect 114 | github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect 115 | github.com/multiformats/go-multibase v0.2.0 // indirect 116 | github.com/multiformats/go-multicodec v0.9.0 // indirect 117 | github.com/multiformats/go-multihash v0.2.3 // indirect 118 | github.com/multiformats/go-multistream v0.5.0 // indirect 119 | github.com/multiformats/go-varint v0.0.7 // indirect 120 | github.com/onsi/ginkgo/v2 v2.15.0 // indirect 121 | github.com/opencontainers/runtime-spec v1.2.0 // indirect 122 | github.com/opentracing/opentracing-go v1.2.0 // indirect 123 | github.com/openzipkin/zipkin-go v0.4.2 // indirect 124 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect 125 | github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect 126 | github.com/pion/datachannel v1.5.5 // indirect 127 | github.com/pion/dtls/v2 v2.2.8 // indirect 128 | github.com/pion/ice/v2 v2.3.11 // indirect 129 | github.com/pion/interceptor v0.1.25 // indirect 130 | github.com/pion/logging v0.2.2 // indirect 131 | github.com/pion/mdns v0.0.9 // indirect 132 | github.com/pion/randutil v0.1.0 // indirect 133 | github.com/pion/rtcp v1.2.13 // indirect 134 | github.com/pion/rtp v1.8.3 // indirect 135 | github.com/pion/sctp v1.8.9 // indirect 136 | github.com/pion/sdp/v3 v3.0.6 // indirect 137 | github.com/pion/srtp/v2 v2.0.18 // indirect 138 | github.com/pion/stun v0.6.1 // indirect 139 | github.com/pion/transport/v2 v2.2.4 // indirect 140 | github.com/pion/turn/v2 v2.1.4 // indirect 141 | github.com/pion/webrtc/v3 v3.2.23 // indirect 142 | github.com/pkg/errors v0.9.1 // indirect 143 | github.com/pmezard/go-difflib v1.0.0 // indirect 144 | github.com/polydawn/refmt v0.89.0 // indirect 145 | github.com/prometheus/client_golang v1.18.0 // indirect 146 | github.com/prometheus/client_model v0.6.0 // indirect 147 | github.com/prometheus/common v0.47.0 // indirect 148 | github.com/prometheus/procfs v0.12.0 // indirect 149 | github.com/quic-go/qpack v0.4.0 // indirect 150 | github.com/quic-go/quic-go v0.41.0 // indirect 151 | github.com/quic-go/webtransport-go v0.6.0 // indirect 152 | github.com/raulk/go-watchdog v1.3.0 // indirect 153 | github.com/samber/lo v1.39.0 // indirect 154 | github.com/spaolacci/murmur3 v1.1.0 // indirect 155 | github.com/stretchr/testify v1.8.4 // indirect 156 | github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect 157 | github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect 158 | github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect 159 | github.com/whyrusleeping/cbor-gen v0.0.0-20240109153615-66e95c3e8a87 // indirect 160 | github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect 161 | github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect 162 | github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect 163 | go.opencensus.io v0.24.0 // indirect 164 | go.opentelemetry.io/otel v1.22.0 // indirect 165 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect 166 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect 167 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect 168 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 // indirect 169 | go.opentelemetry.io/otel/exporters/zipkin v1.21.0 // indirect 170 | go.opentelemetry.io/otel/metric v1.22.0 // indirect 171 | go.opentelemetry.io/otel/sdk v1.21.0 // indirect 172 | go.opentelemetry.io/otel/trace v1.22.0 // indirect 173 | go.opentelemetry.io/proto/otlp v1.0.0 // indirect 174 | go.uber.org/dig v1.17.1 // indirect 175 | go.uber.org/fx v1.20.1 // indirect 176 | go.uber.org/mock v0.4.0 // indirect 177 | go.uber.org/multierr v1.11.0 // indirect 178 | go.uber.org/zap v1.27.0 // indirect 179 | go4.org v0.0.0-20230225012048-214862532bf5 // indirect 180 | golang.org/x/crypto v0.19.0 // indirect 181 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect 182 | golang.org/x/mod v0.15.0 // indirect 183 | golang.org/x/net v0.21.0 // indirect 184 | golang.org/x/sync v0.6.0 // indirect 185 | golang.org/x/sys v0.17.0 // indirect 186 | golang.org/x/text v0.14.0 // indirect 187 | golang.org/x/tools v0.18.0 // indirect 188 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 189 | gonum.org/v1/gonum v0.14.0 // indirect 190 | google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect 191 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect 192 | google.golang.org/grpc v1.60.1 // indirect 193 | google.golang.org/protobuf v1.32.0 // indirect 194 | gopkg.in/yaml.v3 v3.0.1 // indirect 195 | lukechampine.com/blake3 v1.2.1 // indirect 196 | ) 197 | 198 | replace berty.tech/go-ipfs-log => ../ 199 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module berty.tech/go-ipfs-log 2 | 3 | go 1.22 4 | 5 | toolchain go1.22.5 6 | 7 | require ( 8 | github.com/btcsuite/btcd v0.22.1 9 | github.com/hashicorp/golang-lru v1.0.2 10 | github.com/ipfs/boxo v0.20.0 11 | github.com/ipfs/go-cid v0.4.1 12 | github.com/ipfs/go-datastore v0.6.0 13 | github.com/ipfs/go-ipld-cbor v0.1.0 14 | github.com/ipfs/go-ipld-format v0.6.0 15 | github.com/ipfs/go-merkledag v0.11.0 16 | github.com/ipfs/kubo v0.29.0 17 | github.com/libp2p/go-libp2p v0.34.1 18 | github.com/multiformats/go-multibase v0.2.0 19 | github.com/polydawn/refmt v0.89.0 20 | github.com/stretchr/testify v1.9.0 21 | go.uber.org/goleak v1.3.0 22 | golang.org/x/crypto v0.24.0 23 | golang.org/x/sync v0.7.0 24 | ) 25 | 26 | require ( 27 | bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc // indirect 28 | github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect 29 | github.com/Jorropo/jsync v1.0.1 // indirect 30 | github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect 31 | github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect 32 | github.com/benbjohnson/clock v1.3.5 // indirect 33 | github.com/beorn7/perks v1.0.1 // indirect 34 | github.com/blang/semver/v4 v4.0.0 // indirect 35 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 36 | github.com/ceramicnetwork/go-dag-jose v0.1.0 // indirect 37 | github.com/cespare/xxhash v1.1.0 // indirect 38 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 39 | github.com/containerd/cgroups v1.1.0 // indirect 40 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 41 | github.com/crackcomm/go-gitignore v0.0.0-20231225121904-e25f5bc08668 // indirect 42 | github.com/cskr/pubsub v1.0.2 // indirect 43 | github.com/davecgh/go-spew v1.1.1 // indirect 44 | github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect 45 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect 46 | github.com/dgraph-io/badger v1.6.2 // indirect 47 | github.com/dgraph-io/ristretto v0.0.3 // indirect 48 | github.com/docker/go-units v0.5.0 // indirect 49 | github.com/dustin/go-humanize v1.0.1 // indirect 50 | github.com/elastic/gosigar v0.14.2 // indirect 51 | github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect 52 | github.com/felixge/httpsnoop v1.0.4 // indirect 53 | github.com/flynn/noise v1.1.0 // indirect 54 | github.com/francoispqt/gojay v1.2.13 // indirect 55 | github.com/fsnotify/fsnotify v1.6.0 // indirect 56 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 57 | github.com/go-logr/logr v1.4.1 // indirect 58 | github.com/go-logr/stdr v1.2.2 // indirect 59 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 60 | github.com/godbus/dbus/v5 v5.1.0 // indirect 61 | github.com/gogo/protobuf v1.3.2 // indirect 62 | github.com/golang/protobuf v1.5.4 // indirect 63 | github.com/golang/snappy v0.0.4 // indirect 64 | github.com/google/gopacket v1.1.19 // indirect 65 | github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 // indirect 66 | github.com/google/uuid v1.6.0 // indirect 67 | github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect 68 | github.com/gorilla/websocket v1.5.1 // indirect 69 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect 70 | github.com/hashicorp/errwrap v1.1.0 // indirect 71 | github.com/hashicorp/go-multierror v1.1.1 // indirect 72 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 73 | github.com/huin/goupnp v1.3.0 // indirect 74 | github.com/ipfs-shipyard/nopfs v0.0.12 // indirect 75 | github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c // indirect 76 | github.com/ipfs/bbloom v0.0.4 // indirect 77 | github.com/ipfs/go-bitfield v1.1.0 // indirect 78 | github.com/ipfs/go-block-format v0.2.0 // indirect 79 | github.com/ipfs/go-blockservice v0.5.2 // indirect 80 | github.com/ipfs/go-cidutil v0.1.0 // indirect 81 | github.com/ipfs/go-ds-badger v0.3.0 // indirect 82 | github.com/ipfs/go-ds-flatfs v0.5.1 // indirect 83 | github.com/ipfs/go-ds-leveldb v0.5.0 // indirect 84 | github.com/ipfs/go-ds-measure v0.2.0 // indirect 85 | github.com/ipfs/go-fs-lock v0.0.7 // indirect 86 | github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect 87 | github.com/ipfs/go-ipfs-cmds v0.11.0 // indirect 88 | github.com/ipfs/go-ipfs-delay v0.0.1 // indirect 89 | github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect 90 | github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect 91 | github.com/ipfs/go-ipfs-pq v0.0.3 // indirect 92 | github.com/ipfs/go-ipfs-redirects-file v0.1.1 // indirect 93 | github.com/ipfs/go-ipfs-util v0.0.3 // indirect 94 | github.com/ipfs/go-ipld-git v0.1.1 // indirect 95 | github.com/ipfs/go-ipld-legacy v0.2.1 // indirect 96 | github.com/ipfs/go-log v1.0.5 // indirect 97 | github.com/ipfs/go-log/v2 v2.5.1 // indirect 98 | github.com/ipfs/go-metrics-interface v0.0.1 // indirect 99 | github.com/ipfs/go-peertaskqueue v0.8.1 // indirect 100 | github.com/ipfs/go-unixfsnode v1.9.0 // indirect 101 | github.com/ipfs/go-verifcid v0.0.3 // indirect 102 | github.com/ipld/go-car v0.6.2 // indirect 103 | github.com/ipld/go-car/v2 v2.13.1 // indirect 104 | github.com/ipld/go-codec-dagpb v1.6.0 // indirect 105 | github.com/ipld/go-ipld-prime v0.21.0 // indirect 106 | github.com/jackpal/go-nat-pmp v1.0.2 // indirect 107 | github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect 108 | github.com/jbenet/goprocess v0.1.4 // indirect 109 | github.com/klauspost/compress v1.17.8 // indirect 110 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 111 | github.com/koron/go-ssdp v0.0.4 // indirect 112 | github.com/libp2p/go-buffer-pool v0.1.0 // indirect 113 | github.com/libp2p/go-cidranger v1.1.0 // indirect 114 | github.com/libp2p/go-doh-resolver v0.4.0 // indirect 115 | github.com/libp2p/go-flow-metrics v0.1.0 // indirect 116 | github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect 117 | github.com/libp2p/go-libp2p-kad-dht v0.25.2 // indirect 118 | github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect 119 | github.com/libp2p/go-libp2p-pubsub v0.11.0 // indirect 120 | github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect 121 | github.com/libp2p/go-libp2p-record v0.2.0 // indirect 122 | github.com/libp2p/go-libp2p-routing-helpers v0.7.3 // indirect 123 | github.com/libp2p/go-libp2p-testing v0.12.0 // indirect 124 | github.com/libp2p/go-libp2p-xor v0.1.0 // indirect 125 | github.com/libp2p/go-msgio v0.3.0 // indirect 126 | github.com/libp2p/go-nat v0.2.0 // indirect 127 | github.com/libp2p/go-netroute v0.2.1 // indirect 128 | github.com/libp2p/go-reuseport v0.4.0 // indirect 129 | github.com/libp2p/go-yamux/v4 v4.0.1 // indirect 130 | github.com/libp2p/zeroconf/v2 v2.2.0 // indirect 131 | github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect 132 | github.com/mattn/go-isatty v0.0.20 // indirect 133 | github.com/miekg/dns v1.1.59 // indirect 134 | github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect 135 | github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect 136 | github.com/minio/sha256-simd v1.0.1 // indirect 137 | github.com/mitchellh/go-homedir v1.1.0 // indirect 138 | github.com/mr-tron/base58 v1.2.0 // indirect 139 | github.com/multiformats/go-base32 v0.1.0 // indirect 140 | github.com/multiformats/go-base36 v0.2.0 // indirect 141 | github.com/multiformats/go-multiaddr v0.12.4 // indirect 142 | github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect 143 | github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect 144 | github.com/multiformats/go-multicodec v0.9.0 // indirect 145 | github.com/multiformats/go-multihash v0.2.3 // indirect 146 | github.com/multiformats/go-multistream v0.5.0 // indirect 147 | github.com/multiformats/go-varint v0.0.7 // indirect 148 | github.com/onsi/ginkgo/v2 v2.17.3 // indirect 149 | github.com/opencontainers/runtime-spec v1.2.0 // indirect 150 | github.com/opentracing/opentracing-go v1.2.0 // indirect 151 | github.com/openzipkin/zipkin-go v0.4.3 // indirect 152 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect 153 | github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect 154 | github.com/pion/datachannel v1.5.6 // indirect 155 | github.com/pion/dtls/v2 v2.2.11 // indirect 156 | github.com/pion/ice/v2 v2.3.24 // indirect 157 | github.com/pion/interceptor v0.1.29 // indirect 158 | github.com/pion/logging v0.2.2 // indirect 159 | github.com/pion/mdns v0.0.12 // indirect 160 | github.com/pion/randutil v0.1.0 // indirect 161 | github.com/pion/rtcp v1.2.14 // indirect 162 | github.com/pion/rtp v1.8.6 // indirect 163 | github.com/pion/sctp v1.8.16 // indirect 164 | github.com/pion/sdp/v3 v3.0.9 // indirect 165 | github.com/pion/srtp/v2 v2.0.18 // indirect 166 | github.com/pion/stun v0.6.1 // indirect 167 | github.com/pion/transport/v2 v2.2.5 // indirect 168 | github.com/pion/turn/v2 v2.1.6 // indirect 169 | github.com/pion/webrtc/v3 v3.2.40 // indirect 170 | github.com/pkg/errors v0.9.1 // indirect 171 | github.com/pmezard/go-difflib v1.0.0 // indirect 172 | github.com/prometheus/client_golang v1.19.1 // indirect 173 | github.com/prometheus/client_model v0.6.1 // indirect 174 | github.com/prometheus/common v0.53.0 // indirect 175 | github.com/prometheus/procfs v0.15.0 // indirect 176 | github.com/quic-go/qpack v0.4.0 // indirect 177 | github.com/quic-go/quic-go v0.44.0 // indirect 178 | github.com/quic-go/webtransport-go v0.8.0 // indirect 179 | github.com/raulk/go-watchdog v1.3.0 // indirect 180 | github.com/samber/lo v1.39.0 // indirect 181 | github.com/spaolacci/murmur3 v1.1.0 // indirect 182 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect 183 | github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect 184 | github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect 185 | github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect 186 | github.com/whyrusleeping/cbor-gen v0.1.1 // indirect 187 | github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect 188 | github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect 189 | github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect 190 | go.opencensus.io v0.24.0 // indirect 191 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect 192 | go.opentelemetry.io/otel v1.26.0 // indirect 193 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect 194 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect 195 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect 196 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.26.0 // indirect 197 | go.opentelemetry.io/otel/exporters/zipkin v1.26.0 // indirect 198 | go.opentelemetry.io/otel/metric v1.26.0 // indirect 199 | go.opentelemetry.io/otel/sdk v1.26.0 // indirect 200 | go.opentelemetry.io/otel/trace v1.26.0 // indirect 201 | go.opentelemetry.io/proto/otlp v1.2.0 // indirect 202 | go.uber.org/atomic v1.11.0 // indirect 203 | go.uber.org/dig v1.17.1 // indirect 204 | go.uber.org/fx v1.21.1 // indirect 205 | go.uber.org/mock v0.4.0 // indirect 206 | go.uber.org/multierr v1.11.0 // indirect 207 | go.uber.org/zap v1.27.0 // indirect 208 | go4.org v0.0.0-20230225012048-214862532bf5 // indirect 209 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 210 | golang.org/x/mod v0.17.0 // indirect 211 | golang.org/x/net v0.26.0 // indirect 212 | golang.org/x/sys v0.21.0 // indirect 213 | golang.org/x/text v0.16.0 // indirect 214 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 215 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 216 | gonum.org/v1/gonum v0.15.0 // indirect 217 | google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291 // indirect 218 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect 219 | google.golang.org/grpc v1.64.1 // indirect 220 | google.golang.org/protobuf v1.34.1 // indirect 221 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect 222 | gopkg.in/yaml.v3 v3.0.1 // indirect 223 | lukechampine.com/blake3 v1.3.0 // indirect 224 | ) 225 | -------------------------------------------------------------------------------- /identityprovider/identities.go: -------------------------------------------------------------------------------- 1 | package identityprovider // import "berty.tech/go-ipfs-log/identityprovider" 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | 7 | "github.com/btcsuite/btcd/btcec" 8 | "github.com/libp2p/go-libp2p/core/crypto" 9 | 10 | "berty.tech/go-ipfs-log/errmsg" 11 | "berty.tech/go-ipfs-log/keystore" 12 | ) 13 | 14 | var supportedTypes = map[string]func(*CreateIdentityOptions) Interface{ 15 | "orbitdb": NewOrbitDBIdentityProvider, 16 | } 17 | 18 | type Identities struct { 19 | keyStore keystore.Interface 20 | } 21 | 22 | func getHandlerFor(typeName string) (func(*CreateIdentityOptions) Interface, error) { 23 | if !IsSupported(typeName) { 24 | return nil, errmsg.ErrIdentityProviderNotSupported 25 | } 26 | 27 | return supportedTypes[typeName], nil 28 | } 29 | 30 | func newIdentities(keyStore keystore.Interface) *Identities { 31 | return &Identities{ 32 | keyStore: keyStore, 33 | } 34 | } 35 | 36 | func (i *Identities) Sign(ctx context.Context, identity *Identity, data []byte) ([]byte, error) { 37 | privKey, err := i.keyStore.GetKey(ctx, identity.ID) 38 | if err != nil { 39 | return nil, errmsg.ErrKeyNotInKeystore.Wrap(err) 40 | } 41 | 42 | sig, err := i.keyStore.Sign(privKey, data) 43 | if err != nil { 44 | return nil, errmsg.ErrSigSign.Wrap(err) 45 | } 46 | 47 | return sig, nil 48 | } 49 | 50 | // Verify checks a signature. 51 | func (i *Identities) Verify(signature []byte, publicKey crypto.PubKey, data []byte) (bool, error) { 52 | // TODO: Check why this is related to an identity 53 | return publicKey.Verify(data, signature) 54 | } 55 | 56 | //type MigrateOptions struct { 57 | // TargetPath string 58 | // TargetID string 59 | //} 60 | 61 | func compressedToUncompressedS256Key(pubKeyBytes []byte) ([]byte, error) { 62 | pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) 63 | if err != nil { 64 | return nil, errmsg.ErrNotSecp256k1PubKey.Wrap(err) 65 | } 66 | 67 | if !btcec.IsCompressedPubKey(pubKeyBytes) { 68 | return pubKeyBytes, nil 69 | } 70 | 71 | return pubKey.SerializeUncompressed(), nil 72 | } 73 | 74 | // CreateIdentity creates a new Identity. 75 | func (i *Identities) CreateIdentity(ctx context.Context, options *CreateIdentityOptions) (*Identity, error) { 76 | NewIdentityProvider, err := getHandlerFor(options.Type) 77 | if err != nil { 78 | return nil, errmsg.ErrIdentityProviderNotSupported.Wrap(err) 79 | } 80 | 81 | identityProvider := NewIdentityProvider(options) 82 | id, err := identityProvider.GetID(ctx, options) 83 | if err != nil { 84 | return nil, errmsg.ErrIdentityUnknown.Wrap(err) 85 | } 86 | 87 | // FIXME ? 88 | //if options.Migrate != nil { 89 | // if err := options.Migrate(&MigrateOptions{ TargetPath: i.keyStore.Path, TargetID: id }); err != nil { 90 | // return nil, err 91 | // } 92 | //} 93 | 94 | publicKey, idSignature, err := i.signID(ctx, id) 95 | if err != nil { 96 | return nil, errmsg.ErrSigSign.Wrap(err) 97 | } 98 | 99 | publicKeyBytes, err := publicKey.Raw() 100 | if err != nil { 101 | return nil, errmsg.ErrNotSecp256k1PubKey.Wrap(err) 102 | } 103 | 104 | // JS version of IPFS Log expects an uncompressed Secp256k1 key 105 | if publicKey.Type().String() == "Secp256k1" { 106 | publicKeyBytes, err = compressedToUncompressedS256Key(publicKeyBytes) 107 | if err != nil { 108 | return nil, errmsg.ErrNotSecp256k1PubKey.Wrap(err) 109 | } 110 | } 111 | 112 | pubKeyIDSignature, err := identityProvider.SignIdentity(ctx, append(publicKeyBytes, idSignature...), options.ID) 113 | if err != nil { 114 | return nil, errmsg.ErrIdentityCreationFailed.Wrap(err) 115 | } 116 | 117 | return &Identity{ 118 | ID: id, 119 | PublicKey: publicKeyBytes, 120 | Signatures: &IdentitySignature{ 121 | ID: idSignature, 122 | PublicKey: pubKeyIDSignature, 123 | }, 124 | Type: identityProvider.GetType(), 125 | Provider: identityProvider, 126 | }, nil 127 | } 128 | 129 | func (i *Identities) signID(ctx context.Context, id string) (crypto.PubKey, []byte, error) { 130 | privKey, err := i.keyStore.GetKey(ctx, id) 131 | if err != nil { 132 | privKey, err = i.keyStore.CreateKey(ctx, id) 133 | 134 | if err != nil { 135 | return nil, nil, errmsg.ErrSigSign.Wrap(err) 136 | } 137 | } 138 | 139 | idSignature, err := i.keyStore.Sign(privKey, []byte(id)) 140 | if err != nil { 141 | return nil, nil, errmsg.ErrSigSign.Wrap(err) 142 | } 143 | 144 | return privKey.GetPublic(), idSignature, nil 145 | } 146 | 147 | // VerifyIdentity checks an identity. 148 | func (i *Identities) VerifyIdentity(identity *Identity) error { 149 | pubKey, err := identity.GetPublicKey() 150 | if err != nil { 151 | return errmsg.ErrPubKeyDeserialization.Wrap(err) 152 | } 153 | 154 | idBytes, err := hex.DecodeString(identity.ID) 155 | if err != nil { 156 | return errmsg.ErrIdentityDeserialization.Wrap(err) 157 | } 158 | 159 | err = i.keyStore.Verify( 160 | identity.Signatures.ID, 161 | pubKey, 162 | idBytes, 163 | ) 164 | if err != nil { 165 | return errmsg.ErrSigNotVerified.Wrap(err) 166 | } 167 | 168 | identityProvider, err := getHandlerFor(identity.Type) 169 | if err != nil { 170 | return errmsg.ErrSigNotVerified.Wrap(err) 171 | } 172 | 173 | return identityProvider(nil).VerifyIdentity(identity) 174 | } 175 | 176 | // CreateIdentity creates a new identity. 177 | func CreateIdentity(ctx context.Context, options *CreateIdentityOptions) (*Identity, error) { 178 | ks := options.Keystore 179 | if ks == nil { 180 | return nil, errmsg.ErrKeystoreNotDefined 181 | } 182 | 183 | identities := newIdentities(ks) 184 | 185 | return identities.CreateIdentity(ctx, options) 186 | } 187 | 188 | // IsSupported checks if an identity type is supported. 189 | func IsSupported(typeName string) bool { 190 | _, ok := supportedTypes[typeName] 191 | 192 | return ok 193 | } 194 | 195 | // AddIdentityProvider registers an new identity provider. 196 | func AddIdentityProvider(identityProvider func(*CreateIdentityOptions) Interface) error { 197 | if identityProvider == nil { 198 | return errmsg.ErrIdentityProviderNotDefined 199 | } 200 | 201 | supportedTypes[identityProvider(nil).GetType()] = identityProvider 202 | 203 | return nil 204 | } 205 | 206 | // RemoveIdentityProvider unregisters an identity provider. 207 | func RemoveIdentityProvider(name string) { 208 | delete(supportedTypes, name) 209 | } 210 | -------------------------------------------------------------------------------- /identityprovider/identity.go: -------------------------------------------------------------------------------- 1 | // Package identityprovider defines a default identity provider for IPFS Log and OrbitDB. 2 | package identityprovider // import "berty.tech/go-ipfs-log/identityprovider" 3 | 4 | import ( 5 | ic "github.com/libp2p/go-libp2p/core/crypto" 6 | ) 7 | 8 | type IdentitySignature struct { 9 | ID []byte `json:"id,omitempty"` 10 | PublicKey []byte `json:"publicKey,omitempty"` 11 | } 12 | 13 | type Identity struct { 14 | ID string `json:"id,omitempty"` 15 | PublicKey []byte `json:"publicKey,omitempty"` 16 | Signatures *IdentitySignature `json:"signatures,omitempty"` 17 | Type string `json:"type,omitempty"` 18 | Provider Interface 19 | } 20 | 21 | // Filtered gets fields that should be present in the CBOR representation of identity. 22 | func (i *Identity) Filtered() *Identity { 23 | return &Identity{ 24 | ID: i.ID, 25 | PublicKey: i.PublicKey, 26 | Signatures: i.Signatures, 27 | Type: i.Type, 28 | } 29 | } 30 | 31 | // GetPublicKey returns the public key of an identity. 32 | func (i *Identity) GetPublicKey() (ic.PubKey, error) { 33 | return ic.UnmarshalPublicKey(i.PublicKey) 34 | } 35 | -------------------------------------------------------------------------------- /identityprovider/interface.go: -------------------------------------------------------------------------------- 1 | package identityprovider // import "berty.tech/go-ipfs-log/identityprovider" 2 | 3 | import ( 4 | "context" 5 | 6 | "berty.tech/go-ipfs-log/keystore" 7 | crypto "github.com/libp2p/go-libp2p/core/crypto" 8 | ) 9 | 10 | type CreateIdentityOptions struct { 11 | IdentityKeysPath string 12 | Type string 13 | Keystore keystore.Interface 14 | //Migrate func(*MigrateOptions) error 15 | ID string 16 | } 17 | 18 | type Interface interface { 19 | // GetID returns id of identity (to be signed by orbit-db public key). 20 | GetID(context.Context, *CreateIdentityOptions) (string, error) 21 | 22 | // SignIdentity returns signature of OrbitDB public key signature. 23 | SignIdentity(ctx context.Context, data []byte, id string) ([]byte, error) 24 | 25 | // GetType returns the type for this identity provider. 26 | GetType() string 27 | 28 | // VerifyIdentity checks an identity. 29 | VerifyIdentity(identity *Identity) error 30 | 31 | // Sign will sign a value. 32 | Sign(ctx context.Context, identity *Identity, bytes []byte) ([]byte, error) 33 | 34 | // UnmarshalPublicKey will provide a crypto.PubKey from a key bytes. 35 | UnmarshalPublicKey(data []byte) (crypto.PubKey, error) 36 | } 37 | -------------------------------------------------------------------------------- /identityprovider/orbitdb.go: -------------------------------------------------------------------------------- 1 | package identityprovider // import "berty.tech/go-ipfs-log/identityprovider" 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | 7 | "github.com/libp2p/go-libp2p/core/crypto" 8 | 9 | "berty.tech/go-ipfs-log/errmsg" 10 | "berty.tech/go-ipfs-log/keystore" 11 | ) 12 | 13 | type OrbitDBIdentityProvider struct { 14 | keystore keystore.Interface 15 | } 16 | 17 | // VerifyIdentity checks an OrbitDB identity. 18 | func (p *OrbitDBIdentityProvider) VerifyIdentity(_ *Identity) error { 19 | return nil 20 | } 21 | 22 | // NewOrbitDBIdentityProvider creates a new identity for use with OrbitDB. 23 | func NewOrbitDBIdentityProvider(options *CreateIdentityOptions) Interface { 24 | return &OrbitDBIdentityProvider{ 25 | keystore: options.Keystore, 26 | } 27 | } 28 | 29 | // GetID returns the identity's ID. 30 | func (p *OrbitDBIdentityProvider) GetID(ctx context.Context, options *CreateIdentityOptions) (string, error) { 31 | private, err := p.keystore.GetKey(ctx, options.ID) 32 | if err != nil || private == nil { 33 | private, err = p.keystore.CreateKey(ctx, options.ID) 34 | if err != nil { 35 | return "", errmsg.ErrKeyStoreCreateEntry.Wrap(err) 36 | } 37 | } 38 | 39 | pubBytes, err := private.GetPublic().Raw() 40 | if err != nil { 41 | return "", errmsg.ErrPubKeySerialization.Wrap(err) 42 | } 43 | 44 | return hex.EncodeToString(pubBytes), nil 45 | } 46 | 47 | // SignIdentity signs an OrbitDB identity. 48 | func (p *OrbitDBIdentityProvider) SignIdentity(ctx context.Context, data []byte, id string) ([]byte, error) { 49 | key, err := p.keystore.GetKey(ctx, id) 50 | if err != nil { 51 | return nil, errmsg.ErrKeyNotInKeystore 52 | } 53 | 54 | //data, _ = hex.DecodeString(hex.EncodeToString(data)) 55 | 56 | // FIXME? Data is a unicode encoded hex as a byte (source lib uses Buffer.from(hexStr) instead of Buffer.from(hexStr, "hex")) 57 | data = []byte(hex.EncodeToString(data)) 58 | 59 | signature, err := key.Sign(data) 60 | if err != nil { 61 | return nil, errmsg.ErrSigSign.Wrap(err) 62 | } 63 | 64 | return signature, nil 65 | } 66 | 67 | // Sign signs a value using the current. 68 | func (p *OrbitDBIdentityProvider) Sign(ctx context.Context, identity *Identity, data []byte) ([]byte, error) { 69 | key, err := p.keystore.GetKey(ctx, identity.ID) 70 | if err != nil { 71 | return nil, errmsg.ErrKeyNotInKeystore.Wrap(err) 72 | } 73 | 74 | sig, err := key.Sign(data) 75 | if err != nil { 76 | return nil, errmsg.ErrSigSign.Wrap(err) 77 | } 78 | 79 | return sig, nil 80 | } 81 | 82 | func (p *OrbitDBIdentityProvider) UnmarshalPublicKey(data []byte) (crypto.PubKey, error) { 83 | pubKey, err := crypto.UnmarshalSecp256k1PublicKey(data) 84 | if err != nil { 85 | return nil, errmsg.ErrInvalidPubKeyFormat 86 | } 87 | 88 | return pubKey, nil 89 | } 90 | 91 | // GetType returns the current identity type. 92 | func (*OrbitDBIdentityProvider) GetType() string { 93 | return "orbitdb" 94 | } 95 | 96 | var _ Interface = &OrbitDBIdentityProvider{} 97 | -------------------------------------------------------------------------------- /iface/doc.go: -------------------------------------------------------------------------------- 1 | package iface 2 | -------------------------------------------------------------------------------- /iface/iface.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/ipfs/go-cid" 8 | format "github.com/ipfs/go-ipld-format" 9 | coreiface "github.com/ipfs/kubo/core/coreiface" 10 | 11 | "berty.tech/go-ipfs-log/accesscontroller" 12 | "berty.tech/go-ipfs-log/identityprovider" 13 | ) 14 | 15 | const KeyEncryptedLinks = "encrypted_links" 16 | const KeyEncryptedLinksNonce = "encrypted_links_nonce" 17 | 18 | type WriteOpts struct { 19 | Pin bool 20 | EncryptedLinks string 21 | EncryptedLinksNonce string 22 | } 23 | type ExcludeFunc func(hash cid.Cid) bool 24 | 25 | type FetchOptions struct { 26 | Length *int 27 | ShouldExclude ExcludeFunc 28 | Exclude []IPFSLogEntry 29 | Concurrency int 30 | Timeout time.Duration 31 | // @FIXME(gfanton): progress chan is close automatically by IpfsLog 32 | ProgressChan chan IPFSLogEntry 33 | Provider identityprovider.Interface 34 | IO IO 35 | } 36 | 37 | type IO interface { 38 | Write(ctx context.Context, ipfs coreiface.CoreAPI, obj interface{}, opts *WriteOpts) (cid.Cid, error) 39 | Read(ctx context.Context, ipfs coreiface.CoreAPI, contentIdentifier cid.Cid) (format.Node, error) 40 | DecodeRawEntry(node format.Node, hash cid.Cid, p identityprovider.Interface) (IPFSLogEntry, error) 41 | DecodeRawJSONLog(node format.Node) (*JSONLog, error) 42 | } 43 | 44 | type IOPreSign interface { 45 | IO 46 | PreSign(entry IPFSLogEntry) (IPFSLogEntry, error) 47 | } 48 | 49 | type LogOptions struct { 50 | ID string 51 | AccessController accesscontroller.Interface 52 | Entries IPFSLogOrderedEntries 53 | Heads []IPFSLogEntry 54 | Clock IPFSLogLamportClock 55 | SortFn func(a, b IPFSLogEntry) (int, error) 56 | Concurrency uint 57 | IO IO 58 | } 59 | 60 | type CreateEntryOptions struct { 61 | Pin bool 62 | PreSigned bool 63 | } 64 | 65 | type JSONLog struct { 66 | ID string 67 | Heads []cid.Cid 68 | } 69 | 70 | type IteratorOptions struct { 71 | GT cid.Cid 72 | GTE cid.Cid 73 | LT []cid.Cid 74 | LTE []cid.Cid 75 | Amount *int 76 | } 77 | 78 | type Snapshot struct { 79 | ID string 80 | Heads []cid.Cid 81 | Values []IPFSLogEntry 82 | Clock IPFSLogLamportClock 83 | } 84 | 85 | type AppendOptions struct { 86 | PointerCount int 87 | Pin bool 88 | } 89 | 90 | type IPFSLog interface { 91 | GetID() string 92 | Append(ctx context.Context, payload []byte, opts *AppendOptions) (IPFSLogEntry, error) 93 | Iterator(options *IteratorOptions, output chan<- IPFSLogEntry) error 94 | Join(otherLog IPFSLog, size int) (IPFSLog, error) 95 | ToString(payloadMapper func(IPFSLogEntry) string) string 96 | ToSnapshot() *Snapshot 97 | ToMultihash(ctx context.Context) (cid.Cid, error) 98 | Values() IPFSLogOrderedEntries 99 | ToJSONLog() *JSONLog 100 | Heads() IPFSLogOrderedEntries 101 | GetEntries() IPFSLogOrderedEntries 102 | RawHeads() IPFSLogOrderedEntries 103 | SetIdentity(identity *identityprovider.Identity) 104 | IO() IO 105 | 106 | Len() int 107 | Get(c cid.Cid) (IPFSLogEntry, bool) 108 | } 109 | 110 | type EntrySortFn func(IPFSLogEntry, IPFSLogEntry) (int, error) 111 | 112 | type IPFSLogOrderedEntries interface { 113 | // Merge will fusion two OrderedMap of entries. 114 | Merge(other IPFSLogOrderedEntries) IPFSLogOrderedEntries 115 | 116 | // Copy creates a copy of an OrderedMap. 117 | Copy() IPFSLogOrderedEntries 118 | 119 | // Get retrieves an Entry using its key. 120 | Get(key string) (IPFSLogEntry, bool) 121 | 122 | // UnsafeGet retrieves an Entry using its key, returns nil if not found. 123 | UnsafeGet(key string) IPFSLogEntry 124 | 125 | // Set defines an Entry in the map for a given key. 126 | Set(key string, value IPFSLogEntry) 127 | 128 | // Slice returns an ordered slice of the values existing in the map. 129 | Slice() []IPFSLogEntry 130 | 131 | // Keys retrieves the ordered list of keys in the map. 132 | Keys() []string 133 | 134 | // Len gets the length of the map. 135 | Len() int 136 | 137 | // At gets an item at the given index in the map, returns nil if not found. 138 | At(index uint) IPFSLogEntry 139 | 140 | Reverse() IPFSLogOrderedEntries 141 | } 142 | 143 | type IPFSLogEntry interface { 144 | accesscontroller.LogEntry 145 | 146 | New() IPFSLogEntry 147 | Copy() IPFSLogEntry 148 | 149 | GetLogID() string 150 | GetNext() []cid.Cid 151 | GetRefs() []cid.Cid 152 | GetV() uint64 153 | GetKey() []byte 154 | GetSig() []byte 155 | GetHash() cid.Cid 156 | GetClock() IPFSLogLamportClock 157 | GetAdditionalData() map[string]string 158 | 159 | SetPayload([]byte) 160 | SetLogID(string) 161 | SetNext([]cid.Cid) 162 | SetRefs([]cid.Cid) 163 | SetV(uint64) 164 | SetKey([]byte) 165 | SetSig([]byte) 166 | SetIdentity(*identityprovider.Identity) 167 | SetHash(cid.Cid) 168 | SetClock(IPFSLogLamportClock) 169 | SetAdditionalDataValue(key string, value string) 170 | 171 | IsValid() bool 172 | Verify(identity identityprovider.Interface, io IO) error 173 | Equals(b IPFSLogEntry) bool 174 | IsParent(b IPFSLogEntry) bool 175 | Defined() bool 176 | } 177 | 178 | type IPFSLogLamportClock interface { 179 | New() IPFSLogLamportClock 180 | Defined() bool 181 | 182 | GetID() []byte 183 | GetTime() int 184 | 185 | SetID([]byte) 186 | SetTime(int) 187 | 188 | Tick() IPFSLogLamportClock 189 | Merge(clock IPFSLogLamportClock) IPFSLogLamportClock 190 | Compare(b IPFSLogLamportClock) int 191 | } 192 | 193 | type Hashable struct { 194 | Hash interface{} 195 | ID string 196 | Payload []byte 197 | Next []string 198 | Refs []string 199 | V uint64 200 | Clock IPFSLogLamportClock 201 | Key []byte 202 | AdditionalData map[string]string 203 | } 204 | -------------------------------------------------------------------------------- /io/cbor/cbor.go: -------------------------------------------------------------------------------- 1 | package cbor 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "math" 8 | 9 | "berty.tech/go-ipfs-log/enc" 10 | "github.com/ipfs/go-ipld-cbor/encoding" 11 | 12 | "github.com/ipfs/boxo/path" 13 | "github.com/ipfs/go-cid" 14 | cbornode "github.com/ipfs/go-ipld-cbor" 15 | format "github.com/ipfs/go-ipld-format" 16 | coreiface "github.com/ipfs/kubo/core/coreiface" 17 | ic "github.com/libp2p/go-libp2p/core/crypto" 18 | "github.com/polydawn/refmt/obj/atlas" 19 | 20 | "berty.tech/go-ipfs-log/errmsg" 21 | "berty.tech/go-ipfs-log/identityprovider" 22 | "berty.tech/go-ipfs-log/iface" 23 | "berty.tech/go-ipfs-log/io/jsonable" 24 | ) 25 | 26 | type IOCbor struct { 27 | debug bool 28 | 29 | refClock iface.IPFSLogLamportClock 30 | refEntry iface.IPFSLogEntry 31 | 32 | constantIdentity *identityprovider.Identity 33 | linkKey enc.SharedKey 34 | atlasEntries []*atlas.AtlasEntry 35 | cborMarshaller encoding.PooledMarshaller 36 | cborUnmarshaller encoding.PooledUnmarshaller 37 | } 38 | 39 | type Options struct { 40 | //ConstantIdentity *identityprovider.Identity 41 | LinkKey enc.SharedKey 42 | } 43 | 44 | func (i *IOCbor) DecodeRawJSONLog(node format.Node) (*iface.JSONLog, error) { 45 | jsonLog := &iface.JSONLog{} 46 | err := cbornode.DecodeInto(node.RawData(), jsonLog) 47 | 48 | if err != nil { 49 | return nil, errmsg.ErrCBOROperationFailed.Wrap(err) 50 | } 51 | 52 | return jsonLog, nil 53 | } 54 | 55 | func (i *IOCbor) DecodeRawEntry(node format.Node, hash cid.Cid, p identityprovider.Interface) (iface.IPFSLogEntry, error) { 56 | obj := &jsonable.EntryV2{} 57 | err := cbornode.DecodeInto(node.RawData(), obj) 58 | if err != nil { 59 | return nil, errmsg.ErrCBOROperationFailed.Wrap(err) 60 | } 61 | 62 | obj, err = i.DecryptLinks(obj) 63 | if err != nil { 64 | return nil, errmsg.ErrDecrypt.Wrap(err) 65 | } 66 | 67 | obj.Hash = hash 68 | 69 | e := i.refEntry.New() 70 | if err := obj.ToPlain(e, p, i.refClock.New); err != nil { 71 | return nil, errmsg.ErrEntryDeserializationFailed.Wrap(err) 72 | } 73 | 74 | e.SetHash(hash) 75 | 76 | if i.constantIdentity != nil { 77 | e.SetIdentity(i.constantIdentity) 78 | e.SetKey(i.constantIdentity.PublicKey) 79 | } 80 | 81 | return e, nil 82 | } 83 | 84 | var _io = (*IOCbor)(nil) 85 | 86 | func IO(refEntry iface.IPFSLogEntry, refClock iface.IPFSLogLamportClock) (*IOCbor, error) { 87 | if _io != nil { 88 | return _io, nil 89 | } 90 | 91 | _io = &IOCbor{ 92 | debug: false, 93 | refClock: refClock, 94 | refEntry: refEntry, 95 | } 96 | 97 | _io.atlasEntries = []*atlas.AtlasEntry{ 98 | atlas.BuildEntry(jsonable.Entry{}). 99 | StructMap(). 100 | AddField("V", atlas.StructMapEntry{SerialName: "v"}). 101 | AddField("LogID", atlas.StructMapEntry{SerialName: "id"}). 102 | AddField("Key", atlas.StructMapEntry{SerialName: "key"}). 103 | AddField("Sig", atlas.StructMapEntry{SerialName: "sig"}). 104 | AddField("Hash", atlas.StructMapEntry{SerialName: "hash"}). 105 | AddField("Next", atlas.StructMapEntry{SerialName: "next"}). 106 | AddField("Refs", atlas.StructMapEntry{SerialName: "refs"}). 107 | AddField("Clock", atlas.StructMapEntry{SerialName: "clock"}). 108 | AddField("Payload", atlas.StructMapEntry{SerialName: "payload"}). 109 | AddField("Identity", atlas.StructMapEntry{SerialName: "identity"}). 110 | AddField("EncryptedLinks", atlas.StructMapEntry{SerialName: "enc_links", OmitEmpty: true}). 111 | AddField("EncryptedLinksNonce", atlas.StructMapEntry{SerialName: "enc_links_nonce", OmitEmpty: true}). 112 | Complete(), 113 | 114 | atlas.BuildEntry(jsonable.EntryV1{}). 115 | StructMap(). 116 | AddField("V", atlas.StructMapEntry{SerialName: "v"}). 117 | AddField("LogID", atlas.StructMapEntry{SerialName: "id"}). 118 | AddField("Key", atlas.StructMapEntry{SerialName: "key"}). 119 | AddField("Sig", atlas.StructMapEntry{SerialName: "sig"}). 120 | AddField("Hash", atlas.StructMapEntry{SerialName: "hash"}). 121 | AddField("Next", atlas.StructMapEntry{SerialName: "next"}). 122 | AddField("Clock", atlas.StructMapEntry{SerialName: "clock"}). 123 | AddField("Payload", atlas.StructMapEntry{SerialName: "payload"}). 124 | AddField("Identity", atlas.StructMapEntry{SerialName: "identity"}). 125 | Complete(), 126 | 127 | atlas.BuildEntry(iface.Hashable{}). 128 | StructMap(). 129 | AddField("Hash", atlas.StructMapEntry{SerialName: "hash"}). 130 | AddField("ID", atlas.StructMapEntry{SerialName: "id"}). 131 | AddField("Payload", atlas.StructMapEntry{SerialName: "payload"}). 132 | AddField("Next", atlas.StructMapEntry{SerialName: "next"}). 133 | AddField("Refs", atlas.StructMapEntry{SerialName: "refs"}). 134 | AddField("V", atlas.StructMapEntry{SerialName: "v"}). 135 | AddField("Clock", atlas.StructMapEntry{SerialName: "clock"}). 136 | AddField("AdditionalData", atlas.StructMapEntry{SerialName: "additional_data", OmitEmpty: true}). 137 | Complete(), 138 | 139 | atlas.BuildEntry(jsonable.LamportClock{}). 140 | StructMap(). 141 | AddField("ID", atlas.StructMapEntry{SerialName: "id"}). 142 | AddField("Time", atlas.StructMapEntry{SerialName: "time"}). 143 | Complete(), 144 | 145 | atlas.BuildEntry(jsonable.Identity{}). 146 | StructMap(). 147 | AddField("ID", atlas.StructMapEntry{SerialName: "id"}). 148 | AddField("Type", atlas.StructMapEntry{SerialName: "type"}). 149 | AddField("PublicKey", atlas.StructMapEntry{SerialName: "publicKey"}). 150 | AddField("Signatures", atlas.StructMapEntry{SerialName: "signatures"}). 151 | Complete(), 152 | 153 | atlas.BuildEntry(jsonable.IdentitySignature{}). 154 | StructMap(). 155 | AddField("ID", atlas.StructMapEntry{SerialName: "id"}). 156 | AddField("PublicKey", atlas.StructMapEntry{SerialName: "publicKey"}). 157 | Complete(), 158 | 159 | atlas.BuildEntry(ic.Secp256k1PublicKey{}). 160 | Transform(). 161 | TransformMarshal(atlas.MakeMarshalTransformFunc( 162 | func(x ic.Secp256k1PublicKey) (string, error) { 163 | keyBytes, err := x.Raw() 164 | if err != nil { 165 | return "", errmsg.ErrNotSecp256k1PubKey.Wrap(err) 166 | } 167 | 168 | return base64.StdEncoding.EncodeToString(keyBytes), nil 169 | })). 170 | TransformUnmarshal(atlas.MakeUnmarshalTransformFunc( 171 | func(x string) (ic.Secp256k1PublicKey, error) { 172 | keyBytes, err := base64.StdEncoding.DecodeString(x) 173 | if err != nil { 174 | return ic.Secp256k1PublicKey{}, errmsg.ErrNotSecp256k1PubKey.Wrap(err) 175 | } 176 | 177 | key, err := ic.UnmarshalSecp256k1PublicKey(keyBytes) 178 | if err != nil { 179 | return ic.Secp256k1PublicKey{}, errmsg.ErrNotSecp256k1PubKey.Wrap(err) 180 | } 181 | secpKey, ok := key.(*ic.Secp256k1PublicKey) 182 | if !ok { 183 | return ic.Secp256k1PublicKey{}, errmsg.ErrNotSecp256k1PubKey 184 | } 185 | 186 | return *secpKey, nil 187 | })). 188 | Complete(), 189 | 190 | atlas.BuildEntry(iface.JSONLog{}). 191 | StructMap(). 192 | AddField("ID", atlas.StructMapEntry{SerialName: "id"}). 193 | AddField("Heads", atlas.StructMapEntry{SerialName: "heads"}). 194 | Complete(), 195 | } 196 | 197 | for _, atlasEntry := range _io.atlasEntries { 198 | cbornode.RegisterCborType(atlasEntry) 199 | } 200 | 201 | _io.createCborMarshaller() 202 | 203 | return _io, nil 204 | } 205 | 206 | func (i *IOCbor) createCborMarshaller() { 207 | i.cborMarshaller = encoding.NewPooledMarshaller(atlas.MustBuild(append(_io.atlasEntries, cidAtlasEntry)...). 208 | WithMapMorphism(atlas.MapMorphism{KeySortMode: atlas.KeySortMode_RFC7049})) 209 | i.cborUnmarshaller = encoding.NewPooledUnmarshaller(atlas.MustBuild(append(_io.atlasEntries, cidAtlasEntry)...). 210 | WithMapMorphism(atlas.MapMorphism{KeySortMode: atlas.KeySortMode_RFC7049})) 211 | } 212 | 213 | func (i *IOCbor) ApplyOptions(options *Options) *IOCbor { 214 | out := &IOCbor{ 215 | debug: i.debug, 216 | refClock: i.refClock, 217 | refEntry: i.refEntry, 218 | atlasEntries: i.atlasEntries, 219 | //constantIdentity: options.ConstantIdentity, 220 | linkKey: options.LinkKey, 221 | } 222 | 223 | out.createCborMarshaller() 224 | 225 | return out 226 | } 227 | 228 | func (i *IOCbor) SetDebug(val bool) { 229 | i.debug = val 230 | } 231 | 232 | // WriteCBOR writes a CBOR representation of a given object in IPFS' DAG. 233 | func (i *IOCbor) Write(ctx context.Context, ipfs coreiface.CoreAPI, obj interface{}, opts *iface.WriteOpts) (cid.Cid, error) { 234 | if opts == nil { 235 | opts = &iface.WriteOpts{} 236 | } 237 | 238 | switch o := obj.(type) { 239 | case iface.IPFSLogEntry: 240 | if i.constantIdentity != nil { 241 | o.SetIdentity(nil) 242 | o.SetKey(nil) 243 | } 244 | 245 | obj = jsonable.ToJsonableEntry(o) 246 | break 247 | } 248 | 249 | cborNode, err := cbornode.WrapObject(obj, math.MaxUint64, -1) 250 | if err != nil { 251 | return cid.Undef, errmsg.ErrCBOROperationFailed.Wrap(err) 252 | } 253 | 254 | if i.debug { 255 | fmt.Printf("\nStr of cbor: %x\n", cborNode.RawData()) 256 | } 257 | 258 | err = ipfs.Dag().Add(ctx, cborNode) 259 | if err != nil { 260 | return cid.Undef, errmsg.ErrIPFSOperationFailed.Wrap(err) 261 | } 262 | 263 | if opts.Pin { 264 | if err = ipfs.Pin().Add(ctx, path.FromCid(cborNode.Cid())); err != nil { 265 | return cid.Undef, errmsg.ErrIPFSOperationFailed.Wrap(err) 266 | } 267 | } 268 | 269 | return cborNode.Cid(), nil 270 | } 271 | 272 | // Read reads a CBOR representation of a given object from IPFS' DAG. 273 | func (i *IOCbor) Read(ctx context.Context, ipfs coreiface.CoreAPI, contentIdentifier cid.Cid) (format.Node, error) { 274 | return ipfs.Dag().Get(ctx, contentIdentifier) 275 | } 276 | 277 | func (i *IOCbor) PreSign(entry iface.IPFSLogEntry) (iface.IPFSLogEntry, error) { 278 | if i.linkKey == nil { 279 | return entry, nil 280 | } 281 | 282 | if len(entry.GetNext()) == 0 && len(entry.GetRefs()) == 0 { 283 | return entry, nil 284 | } 285 | 286 | entry = entry.Copy() 287 | 288 | links := &jsonable.EntryV2{} 289 | links.Next = entry.GetNext() 290 | links.Refs = entry.GetRefs() 291 | 292 | cborPayload, err := i.cborMarshaller.Marshal(links) 293 | if err != nil { 294 | return nil, errmsg.ErrEncrypt.Wrap(fmt.Errorf("unable to cbor entry: %w", err)) 295 | } 296 | 297 | nonce, err := i.linkKey.DeriveNonce(NonceRefForEntry(entry)) 298 | if err != nil { 299 | return nil, errmsg.ErrEncrypt.Wrap(err) 300 | } 301 | 302 | encryptedLinks, err := i.linkKey.SealWithNonce(cborPayload, nonce) 303 | if err != nil { 304 | return nil, errmsg.ErrEncrypt.Wrap(fmt.Errorf("unable to encrypt message")) 305 | } 306 | 307 | entry.SetAdditionalDataValue(iface.KeyEncryptedLinks, base64.StdEncoding.EncodeToString(encryptedLinks)) 308 | entry.SetAdditionalDataValue(iface.KeyEncryptedLinksNonce, base64.StdEncoding.EncodeToString(nonce)) 309 | 310 | return entry, nil 311 | } 312 | 313 | func (i *IOCbor) DecryptLinks(entry *jsonable.EntryV2) (*jsonable.EntryV2, error) { 314 | if i.linkKey == nil || len(entry.EncryptedLinks) == 0 || len(entry.EncryptedLinksNonce) == 0 { 315 | return entry, nil 316 | } 317 | 318 | encryptedLinks, err := base64.StdEncoding.DecodeString(entry.EncryptedLinks) 319 | if err != nil { 320 | return nil, errmsg.ErrEntryDeserializationFailed.Wrap(err) 321 | } 322 | 323 | encryptedLinksNonce, err := base64.StdEncoding.DecodeString(entry.EncryptedLinksNonce) 324 | if err != nil { 325 | return nil, errmsg.ErrEntryDeserializationFailed.Wrap(err) 326 | } 327 | 328 | dec, err := i.linkKey.OpenWithNonce(encryptedLinks, encryptedLinksNonce) 329 | if err != nil { 330 | return nil, errmsg.ErrDecrypt.Wrap(err) 331 | } 332 | 333 | links := &jsonable.EntryV2{} 334 | if err := i.cborUnmarshaller.Unmarshal(dec, links); err != nil { 335 | return nil, errmsg.ErrEncrypt.Wrap(fmt.Errorf("unable to unmarshal decrypted message")) 336 | } 337 | 338 | entry.Next = links.Next 339 | entry.Refs = links.Refs 340 | 341 | return entry, nil 342 | } 343 | 344 | func NonceRefForEntry(entry iface.IPFSLogEntry) []byte { 345 | next := "" 346 | 347 | for _, c := range entry.GetNext() { 348 | next += "-" + c.String() 349 | } 350 | 351 | return []byte(fmt.Sprintf("%s,%s,%s,%s,%d,%s,%d", 352 | next, 353 | entry.GetKey(), 354 | entry.GetPayload(), 355 | entry.GetClock().GetID(), 356 | entry.GetClock().GetTime(), 357 | entry.GetLogID(), 358 | entry.GetV(), 359 | )) 360 | } 361 | -------------------------------------------------------------------------------- /io/cbor/cid.go: -------------------------------------------------------------------------------- 1 | package cbor 2 | 3 | import ( 4 | "github.com/ipfs/go-cid" 5 | cbornode "github.com/ipfs/go-ipld-cbor" 6 | "github.com/polydawn/refmt/obj/atlas" 7 | ) 8 | 9 | var cidAtlasEntry = atlas.BuildEntry(cid.Cid{}). 10 | UseTag(cbornode.CBORTagLink). 11 | Transform(). 12 | TransformMarshal(atlas.MakeMarshalTransformFunc( 13 | castCidToBytes, 14 | )). 15 | TransformUnmarshal(atlas.MakeUnmarshalTransformFunc( 16 | castBytesToCid, 17 | )). 18 | Complete() 19 | 20 | func castBytesToCid(x []byte) (cid.Cid, error) { 21 | if len(x) == 0 { 22 | return cid.Cid{}, cbornode.ErrEmptyLink 23 | } 24 | 25 | // TODO: manually doing multibase checking here since our deps don't 26 | // support binary multibase yet 27 | if x[0] != 0 { 28 | return cid.Cid{}, cbornode.ErrInvalidMultibase 29 | } 30 | 31 | c, err := cid.Cast(x[1:]) 32 | if err != nil { 33 | return cid.Cid{}, cbornode.ErrInvalidLink 34 | } 35 | 36 | return c, nil 37 | } 38 | 39 | func castCidToBytes(link cid.Cid) ([]byte, error) { 40 | if !link.Defined() { 41 | return nil, cbornode.ErrEmptyLink 42 | } 43 | return append([]byte{0}, link.Bytes()...), nil 44 | } 45 | -------------------------------------------------------------------------------- /io/compatibility.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "context" 5 | 6 | "berty.tech/go-ipfs-log/entry" 7 | "berty.tech/go-ipfs-log/iface" 8 | "berty.tech/go-ipfs-log/io/cbor" 9 | "github.com/ipfs/go-cid" 10 | format "github.com/ipfs/go-ipld-format" 11 | coreiface "github.com/ipfs/kubo/core/coreiface" 12 | ) 13 | 14 | type CBOROptions = cbor.Options 15 | 16 | func ReadCBOR(ctx context.Context, ipfs coreiface.CoreAPI, c cid.Cid) (format.Node, error) { 17 | io, err := cbor.IO(&entry.Entry{}, &entry.LamportClock{}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return io.Read(ctx, ipfs, c) 23 | } 24 | 25 | func WriteCBOR(ctx context.Context, ipfs coreiface.CoreAPI, obj interface{}, opts *iface.WriteOpts) (cid.Cid, error) { 26 | io, err := cbor.IO(&entry.Entry{}, &entry.LamportClock{}) 27 | if err != nil { 28 | return cid.Undef, err 29 | } 30 | 31 | return io.Write(ctx, ipfs, obj, opts) 32 | } 33 | 34 | func CBOR() *cbor.IOCbor { 35 | io, err := cbor.IO(&entry.Entry{}, &entry.LamportClock{}) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | return io 41 | } 42 | -------------------------------------------------------------------------------- /io/jsonable/types.go: -------------------------------------------------------------------------------- 1 | package jsonable 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/ipfs/go-cid" 7 | 8 | "berty.tech/go-ipfs-log/errmsg" 9 | "berty.tech/go-ipfs-log/identityprovider" 10 | "berty.tech/go-ipfs-log/iface" 11 | ) 12 | 13 | // Entry CBOR representable version of Entry 14 | type Entry struct { 15 | V uint64 16 | LogID string 17 | Key string 18 | Sig string 19 | Hash interface{} 20 | Next []cid.Cid 21 | Refs []cid.Cid 22 | Clock *LamportClock 23 | Payload string 24 | Identity *Identity 25 | 26 | EncryptedLinks string 27 | EncryptedLinksNonce string 28 | } 29 | 30 | // EntryV0 CBOR representable version of Entry v0 31 | type EntryV0 struct { 32 | Hash *string `json:"hash"` 33 | ID string `json:"id"` 34 | Payload string `json:"payload"` 35 | Next []string `json:"next"` 36 | V uint64 `json:"v"` 37 | Clock *LamportClock `json:"clock"` 38 | Key string `json:"key"` 39 | Sig string `json:"sig"` 40 | } 41 | 42 | // EntryV1 CBOR representable version of Entry v1 43 | type EntryV1 struct { 44 | V uint64 45 | LogID string 46 | Key string 47 | Sig string 48 | Hash interface{} 49 | Next []cid.Cid 50 | Clock *LamportClock 51 | Payload string 52 | Identity *Identity 53 | } 54 | 55 | // EntryV2 CBOR representable version of Entry v2 56 | type EntryV2 = Entry 57 | 58 | // ToPlain converts a CBOR serializable identity signature to a plain IdentitySignature. 59 | func (c *IdentitySignature) ToPlain() (*identityprovider.IdentitySignature, error) { 60 | publicKey, err := hex.DecodeString(c.PublicKey) 61 | if err != nil { 62 | return nil, errmsg.ErrIdentitySigDeserialization.Wrap(err) 63 | } 64 | 65 | id, err := hex.DecodeString(c.ID) 66 | if err != nil { 67 | return nil, errmsg.ErrIdentitySigDeserialization.Wrap(err) 68 | } 69 | 70 | return &identityprovider.IdentitySignature{ 71 | PublicKey: publicKey, 72 | ID: id, 73 | }, nil 74 | } 75 | 76 | func (e *EntryV0) ToPlain(out iface.IPFSLogEntry, _ identityprovider.Interface, newClock func() iface.IPFSLogLamportClock) error { 77 | c := cid.Undef 78 | 79 | if e.Hash != nil { 80 | var err error 81 | 82 | if c, err = cid.Parse(*e.Hash); err != nil { 83 | return err 84 | } 85 | } 86 | 87 | clock := newClock() 88 | if err := e.Clock.ToPlain(clock); err != nil { 89 | return err 90 | } 91 | 92 | sig, err := hex.DecodeString(e.Sig) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | key, err := hex.DecodeString(e.Key) 98 | if err != nil { 99 | return errmsg.ErrKeyDeserialization.Wrap(err) 100 | } 101 | 102 | nextValues := make([]cid.Cid, len(e.Next)) 103 | for i, n := range e.Next { 104 | var err error 105 | 106 | nextValues[i], err = cid.Parse(n) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | 112 | if c.Defined() { 113 | out.SetHash(c) 114 | } 115 | out.SetClock(clock) 116 | out.SetSig(sig) 117 | 118 | out.SetV(e.V) 119 | out.SetLogID(e.ID) 120 | out.SetKey(key) 121 | out.SetSig(sig) 122 | out.SetNext(nextValues) 123 | out.SetClock(clock) 124 | out.SetPayload([]byte(e.Payload)) 125 | 126 | return nil 127 | } 128 | 129 | type IdentitySignature struct { 130 | ID string `json:"id"` 131 | PublicKey string `json:"public_key"` 132 | } 133 | 134 | type Identity struct { 135 | ID string `json:"id"` 136 | PublicKey string `json:"public_key"` 137 | Signatures *IdentitySignature `json:"signatures"` 138 | Type string `json:"type"` 139 | } 140 | 141 | type LamportClock struct { 142 | ID string `json:"id"` 143 | Time int `json:"time"` 144 | } 145 | 146 | // ToPlain converts a CBOR serializable to a plain Identity object. 147 | func (c *Identity) ToPlain(provider identityprovider.Interface) (*identityprovider.Identity, error) { 148 | publicKey, err := hex.DecodeString(c.PublicKey) 149 | if err != nil { 150 | return nil, errmsg.ErrIdentityDeserialization.Wrap(err) 151 | } 152 | 153 | idSignatures, err := c.Signatures.ToPlain() 154 | if err != nil { 155 | return nil, errmsg.ErrIdentityDeserialization.Wrap(err) 156 | } 157 | 158 | return &identityprovider.Identity{ 159 | Signatures: idSignatures, 160 | PublicKey: publicKey, 161 | Type: c.Type, 162 | ID: c.ID, 163 | Provider: provider, 164 | }, nil 165 | } 166 | 167 | // ToJsonableEntry creates a CBOR serializable version of an entry 168 | func ToJsonableEntry(e iface.IPFSLogEntry) interface{} { 169 | identity := (*Identity)(nil) 170 | if e.GetIdentity() != nil { 171 | identity = ToJsonableIdentity(e.GetIdentity()) 172 | } 173 | 174 | switch e.GetV() { 175 | case 0: 176 | h := (*string)(nil) 177 | if e.GetHash().Defined() { 178 | val := e.GetHash().String() 179 | h = &val 180 | } 181 | 182 | nextValues := make([]string, len(e.GetNext())) 183 | for i, n := range e.GetNext() { 184 | nextValues[i] = n.String() 185 | } 186 | 187 | return &EntryV0{ 188 | V: e.GetV(), 189 | ID: e.GetLogID(), 190 | Key: hex.EncodeToString(e.GetKey()), 191 | Sig: hex.EncodeToString(e.GetSig()), 192 | Hash: h, 193 | Next: nextValues, 194 | Clock: ToJsonableLamportClock(e.GetClock()), 195 | Payload: string(e.GetPayload()), 196 | } 197 | case 1: 198 | return &EntryV1{ 199 | V: e.GetV(), 200 | LogID: e.GetLogID(), 201 | Key: hex.EncodeToString(e.GetKey()), 202 | Sig: hex.EncodeToString(e.GetSig()), 203 | Hash: nil, 204 | Next: e.GetNext(), 205 | Clock: ToJsonableLamportClock(e.GetClock()), 206 | Payload: string(e.GetPayload()), 207 | Identity: identity, 208 | } 209 | default: 210 | ret := &EntryV2{ 211 | V: e.GetV(), 212 | LogID: e.GetLogID(), 213 | Key: hex.EncodeToString(e.GetKey()), 214 | Sig: hex.EncodeToString(e.GetSig()), 215 | Hash: nil, 216 | Next: e.GetNext(), 217 | Refs: e.GetRefs(), 218 | Clock: ToJsonableLamportClock(e.GetClock()), 219 | Payload: string(e.GetPayload()), 220 | Identity: identity, 221 | } 222 | 223 | { 224 | add := e.GetAdditionalData() 225 | 226 | encryptedLinks, okEncrypted := add[iface.KeyEncryptedLinks] 227 | encryptedLinksNonce, okEncryptedNonce := add[iface.KeyEncryptedLinksNonce] 228 | 229 | if okEncrypted && okEncryptedNonce { 230 | ret.EncryptedLinks = encryptedLinks 231 | ret.EncryptedLinksNonce = encryptedLinksNonce 232 | 233 | ret.Next = []cid.Cid{} 234 | ret.Refs = []cid.Cid{} 235 | } 236 | } 237 | 238 | return ret 239 | } 240 | } 241 | 242 | func ToJsonableLamportClock(l iface.IPFSLogLamportClock) *LamportClock { 243 | return &LamportClock{ 244 | ID: hex.EncodeToString(l.GetID()), 245 | Time: l.GetTime(), 246 | } 247 | } 248 | 249 | // ToJsonableIdentity converts an identity to a CBOR serializable identity. 250 | func ToJsonableIdentity(id *identityprovider.Identity) *Identity { 251 | return &Identity{ 252 | ID: id.ID, 253 | PublicKey: hex.EncodeToString(id.PublicKey), 254 | Type: id.Type, 255 | Signatures: ToJsonableIdentitySignature(id.Signatures), 256 | } 257 | } 258 | 259 | // ToJsonableIdentitySignature converts to a CBOR serialized identity signature a plain IdentitySignature. 260 | func ToJsonableIdentitySignature(id *identityprovider.IdentitySignature) *IdentitySignature { 261 | return &IdentitySignature{ 262 | ID: hex.EncodeToString(id.ID), 263 | PublicKey: hex.EncodeToString(id.PublicKey), 264 | } 265 | } 266 | 267 | // ToPlain returns a plain Entry from a CBOR serialized version 268 | func (c *Entry) ToPlain(out iface.IPFSLogEntry, provider identityprovider.Interface, newClock func() iface.IPFSLogLamportClock) error { 269 | key, err := hex.DecodeString(c.Key) 270 | if err != nil { 271 | return errmsg.ErrKeyDeserialization.Wrap(err) 272 | } 273 | 274 | sig, err := hex.DecodeString(c.Sig) 275 | if err != nil { 276 | return errmsg.ErrSigDeserialization.Wrap(err) 277 | } 278 | 279 | clock := newClock() 280 | if err := c.Clock.ToPlain(clock); err != nil { 281 | return errmsg.ErrClockDeserialization.Wrap(err) 282 | } 283 | 284 | identity := (*identityprovider.Identity)(nil) 285 | if c.Identity != nil { 286 | var err error 287 | 288 | identity, err = c.Identity.ToPlain(provider) 289 | if err != nil { 290 | return errmsg.ErrIdentityDeserialization.Wrap(err) 291 | } 292 | } 293 | 294 | out.SetV(c.V) 295 | out.SetLogID(c.LogID) 296 | out.SetKey(key) 297 | out.SetSig(sig) 298 | out.SetNext(c.Next) 299 | out.SetRefs(c.Refs) 300 | out.SetClock(clock) 301 | out.SetPayload([]byte(c.Payload)) 302 | out.SetIdentity(identity) 303 | 304 | return nil 305 | } 306 | 307 | func (c *LamportClock) ToPlain(out iface.IPFSLogLamportClock) error { 308 | id, err := hex.DecodeString(c.ID) 309 | if err != nil { 310 | return errmsg.ErrClockDeserialization.Wrap(err) 311 | } 312 | 313 | out.SetID(id) 314 | out.SetTime(c.Time) 315 | 316 | return nil 317 | } 318 | -------------------------------------------------------------------------------- /io/pb/pb.go: -------------------------------------------------------------------------------- 1 | package pb 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/ipfs/go-cid" 8 | format "github.com/ipfs/go-ipld-format" 9 | dag "github.com/ipfs/go-merkledag" 10 | coreiface "github.com/ipfs/kubo/core/coreiface" 11 | 12 | "berty.tech/go-ipfs-log/errmsg" 13 | idp "berty.tech/go-ipfs-log/identityprovider" 14 | "berty.tech/go-ipfs-log/iface" 15 | "berty.tech/go-ipfs-log/io/jsonable" 16 | ) 17 | 18 | type pb struct { 19 | refClock iface.IPFSLogLamportClock 20 | refEntry iface.IPFSLogEntry 21 | } 22 | 23 | func (p *pb) Write(ctx context.Context, ipfs coreiface.CoreAPI, obj interface{}, _ *iface.WriteOpts) (cid.Cid, error) { 24 | var err error 25 | payload := []byte(nil) 26 | 27 | switch o := obj.(type) { 28 | case iface.IPFSLogEntry: 29 | payload, err = json.Marshal(jsonable.ToJsonableEntry(o)) 30 | if err != nil { 31 | return cid.Undef, err 32 | } 33 | break 34 | 35 | case *iface.JSONLog: 36 | payload, err = json.Marshal(o) 37 | if err != nil { 38 | return cid.Undef, err 39 | } 40 | break 41 | } 42 | 43 | node := &dag.ProtoNode{} 44 | node.SetData(payload) 45 | 46 | if err := ipfs.Dag().Add(ctx, node); err != nil { 47 | return cid.Cid{}, err 48 | } 49 | 50 | return node.Cid(), nil 51 | } 52 | 53 | func (p *pb) Read(ctx context.Context, ipfs coreiface.CoreAPI, contentIdentifier cid.Cid) (format.Node, error) { 54 | node, err := ipfs.Dag().Get(ctx, contentIdentifier) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return node, nil 60 | } 61 | 62 | func (p *pb) DecodeRawEntry(node format.Node, hash cid.Cid, idProvider idp.Interface) (iface.IPFSLogEntry, error) { 63 | out := p.refEntry.New() 64 | entry := &jsonable.EntryV0{} 65 | 66 | pbNode, err := dag.DecodeProtobuf(node.RawData()) 67 | if err != nil { 68 | return nil, errmsg.ErrPBReadUnmarshalFailed 69 | } 70 | 71 | if err := json.Unmarshal(pbNode.Data(), entry); err != nil { 72 | return nil, err 73 | } 74 | 75 | if err := entry.ToPlain(out, idProvider, p.refClock.New); err != nil { 76 | return nil, errmsg.ErrEntryDeserializationFailed.Wrap(err) 77 | } 78 | 79 | out.SetHash(hash) 80 | 81 | return out, nil 82 | } 83 | 84 | func (p *pb) DecodeRawJSONLog(node format.Node) (*iface.JSONLog, error) { 85 | jsonLog := &iface.JSONLog{} 86 | err := json.Unmarshal(node.RawData(), jsonLog) 87 | 88 | if err != nil { 89 | return nil, errmsg.ErrCBOROperationFailed.Wrap(err) 90 | } 91 | 92 | return jsonLog, nil 93 | } 94 | 95 | var _io = (*pb)(nil) 96 | 97 | func IO(entry iface.IPFSLogEntry, clock iface.IPFSLogLamportClock) (iface.IO, error) { 98 | if _io != nil { 99 | return _io, nil 100 | } 101 | 102 | _io := &pb{ 103 | refClock: clock, 104 | refEntry: entry, 105 | } 106 | 107 | return _io, nil 108 | } 109 | -------------------------------------------------------------------------------- /keystore/interface.go: -------------------------------------------------------------------------------- 1 | package keystore // import "berty.tech/go-ipfs-log/keystore" 2 | 3 | import ( 4 | "context" 5 | 6 | crypto "github.com/libp2p/go-libp2p/core/crypto" 7 | ) 8 | 9 | type Interface interface { 10 | HasKey(ctx context.Context, id string) (bool, error) 11 | 12 | CreateKey(ctx context.Context, id string) (crypto.PrivKey, error) 13 | 14 | GetKey(ctx context.Context, id string) (crypto.PrivKey, error) 15 | 16 | Sign(pubKey crypto.PrivKey, bytes []byte) ([]byte, error) 17 | 18 | Verify(signature []byte, publicKey crypto.PubKey, data []byte) error 19 | } 20 | -------------------------------------------------------------------------------- /keystore/keystore.go: -------------------------------------------------------------------------------- 1 | // Package keystore defines a local key manager for OrbitDB and IPFS Log. 2 | package keystore // import "berty.tech/go-ipfs-log/keystore" 3 | 4 | import ( 5 | "context" 6 | "crypto/rand" 7 | "encoding/base64" 8 | 9 | lru "github.com/hashicorp/golang-lru" 10 | "github.com/ipfs/go-datastore" 11 | "github.com/libp2p/go-libp2p/core/crypto" 12 | 13 | "berty.tech/go-ipfs-log/errmsg" 14 | ) 15 | 16 | type Keystore struct { 17 | store datastore.Datastore 18 | cache *lru.Cache 19 | } 20 | 21 | // Sign signs a value using a given private key. 22 | func (k *Keystore) Sign(privKey crypto.PrivKey, bytes []byte) ([]byte, error) { 23 | return privKey.Sign(bytes) 24 | } 25 | 26 | // Verify verifies a signature. 27 | func (k *Keystore) Verify(signature []byte, publicKey crypto.PubKey, data []byte) error { 28 | ok, err := publicKey.Verify(data, signature) 29 | if err != nil { 30 | return errmsg.ErrSigNotVerified.Wrap(err) 31 | } 32 | 33 | if !ok { 34 | return errmsg.ErrSigNotVerified 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // NewKeystore creates a new keystore. 41 | func NewKeystore(store datastore.Datastore) (*Keystore, error) { 42 | cache, err := lru.New(128) 43 | if err != nil { 44 | return nil, errmsg.ErrKeyStoreInitFailed.Wrap(err) 45 | } 46 | 47 | return &Keystore{ 48 | store: store, 49 | cache: cache, 50 | }, nil 51 | } 52 | 53 | // HasKey checks whether a given key ID exist in the keystore. 54 | func (k *Keystore) HasKey(ctx context.Context, id string) (bool, error) { 55 | storedKey, ok := k.cache.Peek(id) 56 | 57 | if ok == false { 58 | value, err := k.store.Get(ctx, datastore.NewKey(id)) 59 | if err != nil { 60 | return false, errmsg.ErrKeyNotInKeystore.Wrap(err) 61 | } 62 | 63 | if storedKey != nil { 64 | k.cache.Add(id, base64.StdEncoding.EncodeToString(value)) 65 | } 66 | } 67 | 68 | return storedKey != nil, nil 69 | } 70 | 71 | // CreateKey creates a new key in the key store. 72 | func (k *Keystore) CreateKey(ctx context.Context, id string) (crypto.PrivKey, error) { 73 | // FIXME: I kept Secp256k1 for compatibility with OrbitDB, should we change this? 74 | priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader) 75 | if err != nil { 76 | return nil, errmsg.ErrKeyGenerationFailed.Wrap(err) 77 | } 78 | 79 | keyBytes, err := priv.Raw() 80 | if err != nil { 81 | return nil, errmsg.ErrInvalidPrivKeyFormat.Wrap(err) 82 | } 83 | 84 | if err := k.store.Put(ctx, datastore.NewKey(id), keyBytes); err != nil { 85 | return nil, errmsg.ErrKeyStorePutFailed.Wrap(err) 86 | } 87 | 88 | k.cache.Add(id, base64.StdEncoding.EncodeToString(keyBytes)) 89 | 90 | return priv, nil 91 | } 92 | 93 | // GetKey retrieves a key from the keystore. 94 | func (k *Keystore) GetKey(ctx context.Context, id string) (crypto.PrivKey, error) { 95 | var err error 96 | var keyBytes []byte 97 | 98 | cachedKey, ok := k.cache.Get(id) 99 | if !ok || cachedKey == nil { 100 | keyBytes, err = k.store.Get(ctx, datastore.NewKey(id)) 101 | 102 | if err != nil { 103 | return nil, errmsg.ErrKeyNotInKeystore.Wrap(err) 104 | } 105 | k.cache.Add(id, base64.StdEncoding.EncodeToString(keyBytes)) 106 | } else { 107 | keyBytes, err = base64.StdEncoding.DecodeString(cachedKey.(string)) 108 | if err != nil { 109 | return nil, errmsg.ErrInvalidPrivKeyFormat.Wrap(err) 110 | } 111 | } 112 | 113 | privateKey, err := crypto.UnmarshalSecp256k1PrivateKey(keyBytes) 114 | if err != nil { 115 | return nil, errmsg.ErrInvalidPrivKeyFormat.Wrap(err) 116 | } 117 | 118 | return privateKey, nil 119 | } 120 | 121 | var _ Interface = &Keystore{} 122 | -------------------------------------------------------------------------------- /log_io.go: -------------------------------------------------------------------------------- 1 | package ipfslog // import "berty.tech/go-ipfs-log" 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | coreiface "github.com/ipfs/kubo/core/coreiface" 9 | 10 | "berty.tech/go-ipfs-log/iface" 11 | 12 | "berty.tech/go-ipfs-log/entry/sorting" 13 | 14 | "github.com/ipfs/go-cid" 15 | 16 | "berty.tech/go-ipfs-log/entry" 17 | "berty.tech/go-ipfs-log/errmsg" 18 | // "berty.tech/go-ipfs-log/io" 19 | ) 20 | 21 | type FetchOptions struct { 22 | Length *int 23 | Exclude []iface.IPFSLogEntry 24 | ShouldExclude iface.ExcludeFunc 25 | ProgressChan chan iface.IPFSLogEntry 26 | Timeout time.Duration 27 | Concurrency int 28 | SortFn iface.EntrySortFn 29 | } 30 | 31 | func toMultihash(ctx context.Context, services coreiface.CoreAPI, log *IPFSLog) (cid.Cid, error) { 32 | if log.heads.Len() == 0 { 33 | return cid.Undef, errmsg.ErrEmptyLogSerialization 34 | } 35 | 36 | return log.io.Write(ctx, services, log.ToJSONLog(), nil) 37 | } 38 | 39 | func fromMultihash(ctx context.Context, services coreiface.CoreAPI, hash cid.Cid, options *FetchOptions, io iface.IO) (*Snapshot, error) { 40 | result, err := io.Read(ctx, services, hash) 41 | if err != nil { 42 | return nil, errmsg.ErrCBOROperationFailed.Wrap(err) 43 | } 44 | 45 | logHeads, err := io.DecodeRawJSONLog(result) 46 | if err != nil { 47 | return nil, errmsg.ErrCBOROperationFailed.Wrap(err) 48 | } 49 | 50 | // Use user provided sorting function or the default one 51 | sortFn := sorting.NoZeroes(sorting.LastWriteWins) 52 | if options.SortFn != nil { 53 | sortFn = options.SortFn 54 | } 55 | 56 | entries := entry.FetchAll(ctx, services, logHeads.Heads, &iface.FetchOptions{ 57 | Length: options.Length, 58 | ShouldExclude: options.ShouldExclude, 59 | Exclude: options.Exclude, 60 | Concurrency: options.Concurrency, 61 | Timeout: options.Timeout, 62 | ProgressChan: options.ProgressChan, 63 | IO: io, 64 | }) 65 | 66 | if options.Length != nil && *options.Length > -1 { 67 | sorting.Sort(sortFn, entries, false) 68 | 69 | entries = entrySlice(entries, -*options.Length) 70 | } 71 | 72 | var heads []cid.Cid 73 | for _, e := range entries { 74 | for _, h := range logHeads.Heads { 75 | if h.String() == e.GetHash().String() { 76 | heads = append(heads, e.GetHash()) 77 | } 78 | } 79 | } 80 | 81 | return &Snapshot{ 82 | ID: logHeads.ID, 83 | Values: entries, 84 | Heads: heads, 85 | }, nil 86 | } 87 | 88 | func fromEntryHash(ctx context.Context, services coreiface.CoreAPI, hashes []cid.Cid, options *FetchOptions, io iface.IO) ([]iface.IPFSLogEntry, error) { 89 | if services == nil { 90 | return nil, errmsg.ErrIPFSNotDefined 91 | } 92 | 93 | if options == nil { 94 | return nil, errmsg.ErrFetchOptionsNotDefined 95 | } 96 | 97 | // Fetch given length, return size at least the given input entries 98 | length := -1 99 | if options.Length != nil && *options.Length > -1 { 100 | length = maxInt(*options.Length, 1) 101 | } 102 | 103 | all := entry.FetchParallel(ctx, services, hashes, &iface.FetchOptions{ 104 | Length: options.Length, 105 | Exclude: options.Exclude, 106 | ShouldExclude: options.ShouldExclude, 107 | ProgressChan: options.ProgressChan, 108 | Timeout: options.Timeout, 109 | Concurrency: options.Concurrency, 110 | IO: io, 111 | }) 112 | 113 | sortFn := sorting.NoZeroes(sorting.LastWriteWins) 114 | if options.SortFn != nil { 115 | sortFn = options.SortFn 116 | } 117 | 118 | entries := all 119 | if length > -1 { 120 | sorting.Sort(sortFn, entries, false) 121 | entries = entrySlice(all, -length) 122 | } 123 | 124 | return entries, nil 125 | } 126 | 127 | func fromJSON(ctx context.Context, services coreiface.CoreAPI, jsonLog *iface.JSONLog, options *iface.FetchOptions) (*Snapshot, error) { 128 | if services == nil { 129 | return nil, errmsg.ErrIPFSNotDefined 130 | } 131 | 132 | if options == nil { 133 | return nil, errmsg.ErrFetchOptionsNotDefined 134 | } 135 | 136 | if options.IO == nil { 137 | return nil, errmsg.ErrLogOptionsNotDefined.Wrap(fmt.Errorf("missing IO field in fetch options")) 138 | } 139 | 140 | entries := entry.FetchParallel(ctx, services, jsonLog.Heads, &iface.FetchOptions{ 141 | Length: options.Length, 142 | ProgressChan: options.ProgressChan, 143 | Concurrency: options.Concurrency, 144 | Timeout: options.Timeout, 145 | IO: options.IO, 146 | }) 147 | 148 | sorting.Sort(sorting.Compare, entries, false) 149 | 150 | return &Snapshot{ 151 | ID: jsonLog.ID, 152 | Heads: jsonLog.Heads, 153 | Values: entries, 154 | }, nil 155 | } 156 | 157 | func fromEntry(ctx context.Context, services coreiface.CoreAPI, sourceEntries []iface.IPFSLogEntry, options *iface.FetchOptions) (*Snapshot, error) { 158 | if services == nil { 159 | return nil, errmsg.ErrIPFSNotDefined 160 | } 161 | 162 | if options == nil { 163 | return nil, errmsg.ErrFetchOptionsNotDefined 164 | } 165 | 166 | // Fetch given length, return size at least the given input entries 167 | length := -1 168 | if options.Length != nil && *options.Length > -1 { 169 | length = maxInt(*options.Length, len(sourceEntries)) 170 | } 171 | 172 | // Make sure we pass hashes instead of objects to the fetcher function 173 | var hashes []cid.Cid 174 | for _, e := range sourceEntries { 175 | hashes = append(hashes, e.GetHash()) 176 | } 177 | 178 | // Fetch the entries 179 | entries := entry.FetchParallel(ctx, services, hashes, &iface.FetchOptions{ 180 | Length: &length, 181 | Exclude: options.Exclude, 182 | ProgressChan: options.ProgressChan, 183 | Timeout: options.Timeout, 184 | Concurrency: options.Concurrency, 185 | IO: options.IO, 186 | }) 187 | 188 | // Combine the fetches with the source entries and take only uniques 189 | combined := append(sourceEntries, entries...) 190 | combined = append(combined, options.Exclude...) 191 | uniques := entry.NewOrderedMapFromEntries(combined).Slice() 192 | sorting.Sort(sorting.Compare, uniques, false) 193 | 194 | // Cap the result at the right size by taking the last n entries 195 | var sliced []iface.IPFSLogEntry 196 | 197 | if length > -1 { 198 | sliced = entrySlice(uniques, -length) 199 | } else { 200 | sliced = uniques 201 | } 202 | 203 | missingSourceEntries := entry.Difference(sliced, sourceEntries) 204 | result := append(missingSourceEntries, entrySliceRange(sliced, len(missingSourceEntries), len(sliced))...) 205 | 206 | return &Snapshot{ 207 | ID: result[len(result)-1].GetLogID(), 208 | Values: result, 209 | }, nil 210 | } 211 | 212 | func entrySlice(entries []iface.IPFSLogEntry, index int) []iface.IPFSLogEntry { 213 | if len(entries) == 0 || index >= len(entries) { 214 | return []iface.IPFSLogEntry{} 215 | } 216 | 217 | if index == 0 || (index < 0 && -index >= len(entries)) { 218 | return entries 219 | } 220 | 221 | if index > 0 { 222 | return entries[index:] 223 | } 224 | 225 | return entries[(len(entries) + index):] 226 | } 227 | 228 | func entrySliceRange(entries []iface.IPFSLogEntry, from int, to int) []iface.IPFSLogEntry { 229 | if len(entries) == 0 { 230 | return nil 231 | } 232 | 233 | if from < 0 { 234 | from = len(entries) + from 235 | if from < 0 { 236 | from = 0 237 | } 238 | } 239 | 240 | if to < 0 { 241 | to = len(entries) + to 242 | } 243 | 244 | if from >= len(entries) { 245 | return nil 246 | } 247 | 248 | if to > len(entries) { 249 | to = len(entries) 250 | } 251 | 252 | if from >= to { 253 | return nil 254 | } 255 | 256 | if from == to { 257 | return entries 258 | } 259 | 260 | return entries[from:to] 261 | } 262 | -------------------------------------------------------------------------------- /test/bench_add_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | ipfslog "berty.tech/go-ipfs-log" 9 | idp "berty.tech/go-ipfs-log/identityprovider" 10 | "berty.tech/go-ipfs-log/keystore" 11 | dssync "github.com/ipfs/go-datastore/sync" 12 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func BenchmarkAdd(b *testing.B) { 17 | ctx, cancel := context.WithCancel(context.Background()) 18 | defer cancel() 19 | 20 | m := mocknet.New() 21 | defer m.Close() 22 | 23 | ipfs, closeNode := NewMemoryServices(ctx, b, m) 24 | defer closeNode() 25 | 26 | datastore := dssync.MutexWrap(NewIdentityDataStore(b)) 27 | ks, err := keystore.NewKeystore(datastore) 28 | require.NoError(b, err) 29 | 30 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 31 | Keystore: ks, 32 | ID: "userA", 33 | Type: "orbitdb", 34 | }) 35 | 36 | log, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A"}) 37 | require.NoError(b, err) 38 | 39 | b.ResetTimer() 40 | // Start the main loop 41 | for n := 0; n < b.N; n++ { 42 | _, err = log.Append(ctx, []byte(fmt.Sprintf("%d", n)), nil) 43 | require.NoError(b, err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/bench_join_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | 9 | ipfslog "berty.tech/go-ipfs-log" 10 | idp "berty.tech/go-ipfs-log/identityprovider" 11 | "berty.tech/go-ipfs-log/keystore" 12 | dssync "github.com/ipfs/go-datastore/sync" 13 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func BenchmarkJoin(b *testing.B) { 18 | ctx, cancel := context.WithCancel(context.Background()) 19 | defer cancel() 20 | 21 | m := mocknet.New() 22 | defer m.Close() 23 | ipfs, closeNode := NewMemoryServices(ctx, b, m) 24 | defer closeNode() 25 | 26 | datastore := dssync.MutexWrap(NewIdentityDataStore(b)) 27 | ks, err := keystore.NewKeystore(datastore) 28 | require.NoError(b, err) 29 | 30 | identityA, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 31 | Keystore: ks, 32 | ID: "userA", 33 | Type: "orbitdb", 34 | }) 35 | require.NoError(b, err) 36 | 37 | identityB, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 38 | Keystore: ks, 39 | ID: "userB", 40 | Type: "orbitdb", 41 | }) 42 | require.NoError(b, err) 43 | 44 | logA, err := ipfslog.NewLog(ipfs, identityA, &ipfslog.LogOptions{ID: "A"}) 45 | require.NoError(b, err) 46 | 47 | logB, err := ipfslog.NewLog(ipfs, identityB, &ipfslog.LogOptions{ID: "A"}) 48 | require.NoError(b, err) 49 | 50 | b.ResetTimer() 51 | // Start the main loop 52 | for n := 0; n < b.N; n++ { 53 | wg := sync.WaitGroup{} 54 | wg.Add(2) 55 | 56 | go func() { 57 | _, err := logA.Append(ctx, []byte(fmt.Sprintf("a%d", n)), nil) 58 | require.NoError(b, err) 59 | wg.Done() 60 | }() 61 | 62 | go func() { 63 | _, err := logB.Append(ctx, []byte(fmt.Sprintf("a%d", n)), nil) 64 | require.NoError(b, err) 65 | wg.Done() 66 | }() 67 | 68 | wg.Wait() 69 | 70 | _, err := logA.Join(logB, -1) 71 | require.NoError(b, err) 72 | 73 | _, err = logB.Join(logA, -1) 74 | require.NoError(b, err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/entry_io_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | ipfslog "berty.tech/go-ipfs-log" 9 | "berty.tech/go-ipfs-log/entry" 10 | idp "berty.tech/go-ipfs-log/identityprovider" 11 | "berty.tech/go-ipfs-log/iface" 12 | ks "berty.tech/go-ipfs-log/keystore" 13 | cid "github.com/ipfs/go-cid" 14 | dssync "github.com/ipfs/go-datastore/sync" 15 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestEntryPersistence(t *testing.T) { 20 | ctx, cancel := context.WithCancel(context.Background()) 21 | defer cancel() 22 | 23 | m := mocknet.New() 24 | defer m.Close() 25 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 26 | defer closeNode() 27 | 28 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 29 | keystore, err := ks.NewKeystore(datastore) 30 | require.NoError(t, err) 31 | 32 | var identities []*idp.Identity 33 | 34 | for i := 0; i < 4; i++ { 35 | char := 'A' + i 36 | 37 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 38 | Keystore: keystore, 39 | ID: fmt.Sprintf("user%c", char), 40 | Type: "orbitdb", 41 | }) 42 | require.NoError(t, err) 43 | 44 | identities = append(identities, identity) 45 | } 46 | 47 | t.Run("log with 1 entry", func(t *testing.T) { 48 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 49 | require.NoError(t, err) 50 | e, err := log1.Append(ctx, []byte("one"), nil) 51 | require.NoError(t, err) 52 | 53 | hash := e.GetHash() 54 | res := entry.FetchAll(ctx, ipfs, []cid.Cid{hash}, &entry.FetchOptions{Length: intPtr(1)}) 55 | require.Equal(t, len(res), 1) 56 | }) 57 | 58 | t.Run("log with 2 entries", func(t *testing.T) { 59 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 60 | require.NoError(t, err) 61 | _, err = log1.Append(ctx, []byte("one"), nil) 62 | require.NoError(t, err) 63 | e, err := log1.Append(ctx, []byte("two"), nil) 64 | require.NoError(t, err) 65 | 66 | hash := e.GetHash() 67 | res := entry.FetchAll(ctx, ipfs, []cid.Cid{hash}, &entry.FetchOptions{Length: intPtr(2)}) 68 | require.Equal(t, len(res), 2) 69 | }) 70 | 71 | t.Run("loads max 1 entry from a log of 2 entries", func(t *testing.T) { 72 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 73 | require.NoError(t, err) 74 | _, err = log1.Append(ctx, []byte("one"), nil) 75 | require.NoError(t, err) 76 | e, err := log1.Append(ctx, []byte("two"), nil) 77 | require.NoError(t, err) 78 | 79 | hash := e.GetHash() 80 | res := entry.FetchAll(ctx, ipfs, []cid.Cid{hash}, &entry.FetchOptions{Length: intPtr(1)}) 81 | require.Equal(t, len(res), 1) 82 | }) 83 | 84 | t.Run("log with 100 entries", func(t *testing.T) { 85 | var e iface.IPFSLogEntry 86 | var err error 87 | 88 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 89 | require.NoError(t, err) 90 | for i := 0; i < 100; i++ { 91 | e, err = log1.Append(ctx, []byte(fmt.Sprintf("hello%d", i)), nil) 92 | require.NoError(t, err) 93 | } 94 | 95 | hash := e.GetHash() 96 | res := entry.FetchAll(ctx, ipfs, []cid.Cid{hash}, &entry.FetchOptions{}) 97 | require.Equal(t, len(res), 100) 98 | }) 99 | 100 | t.Run("load only 42 entries from a log with 100 entries", func(t *testing.T) { 101 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 102 | require.NoError(t, err) 103 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 104 | require.NoError(t, err) 105 | 106 | for i := 0; i < 100; i++ { 107 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("hello%d", i)), nil) 108 | require.NoError(t, err) 109 | if i%10 == 0 { 110 | heads := append(entry.FindHeads(log2.Entries), entry.FindHeads(log1.Entries)...) 111 | log2, err = ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: log2.ID, Entries: log2.Values(), Heads: heads}) 112 | require.NoError(t, err) 113 | _, err := log2.Append(ctx, []byte(fmt.Sprintf("hi%d", i)), nil) 114 | require.NoError(t, err) 115 | } 116 | } 117 | 118 | hash, err := log1.ToMultihash(ctx) 119 | require.NoError(t, err) 120 | 121 | res, err := ipfslog.NewFromMultihash(ctx, ipfs, identities[0], hash, &ipfslog.LogOptions{}, &ipfslog.FetchOptions{Length: intPtr(42)}) 122 | require.NoError(t, err) 123 | require.Equal(t, res.Entries.Len(), 42) 124 | }) 125 | 126 | t.Run("load only 99 entries from a log with 100 entries", func(t *testing.T) { 127 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 128 | require.NoError(t, err) 129 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 130 | require.NoError(t, err) 131 | 132 | for i := 0; i < 100; i++ { 133 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("hello%d", i)), nil) 134 | require.NoError(t, err) 135 | if i%10 == 0 { 136 | log2, err = ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: log2.ID, Entries: log2.Values()}) 137 | require.NoError(t, err) 138 | _, err := log2.Append(ctx, []byte(fmt.Sprintf("hi%d", i)), nil) 139 | require.NoError(t, err) 140 | _, err = log2.Join(log1, -1) 141 | require.NoError(t, err) 142 | } 143 | } 144 | 145 | hash, err := log2.ToMultihash(ctx) 146 | require.NoError(t, err) 147 | 148 | res, err := ipfslog.NewFromMultihash(ctx, ipfs, identities[0], hash, &ipfslog.LogOptions{}, &ipfslog.FetchOptions{Length: intPtr(99)}) 149 | require.NoError(t, err) 150 | require.Equal(t, res.Entries.Len(), 99) 151 | }) 152 | 153 | t.Run("load only 10 entries from a log with 100 entries", func(t *testing.T) { 154 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 155 | require.NoError(t, err) 156 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 157 | require.NoError(t, err) 158 | log3, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 159 | require.NoError(t, err) 160 | 161 | for i := 0; i < 100; i++ { 162 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("hello%d", i)), nil) 163 | require.NoError(t, err) 164 | if i%10 == 0 { 165 | log2, err = ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: log2.ID, Entries: log2.Values(), Heads: entry.FindHeads(log2.Entries)}) 166 | require.NoError(t, err) 167 | _, err := log2.Append(ctx, []byte(fmt.Sprintf("hi%d", i)), nil) 168 | require.NoError(t, err) 169 | _, err = log2.Join(log1, -1) 170 | require.NoError(t, err) 171 | } 172 | if i%25 == 0 { 173 | heads := append(entry.FindHeads(log3.Entries), entry.FindHeads(log2.Entries)...) 174 | log3, err = ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: log3.ID, Entries: log3.Values(), Heads: heads}) 175 | require.NoError(t, err) 176 | _, err := log3.Append(ctx, []byte(fmt.Sprintf("--%d", i)), nil) 177 | require.NoError(t, err) 178 | } 179 | } 180 | 181 | _, err = log3.Join(log2, -1) 182 | require.NoError(t, err) 183 | 184 | hash, err := log3.ToMultihash(ctx) 185 | require.NoError(t, err) 186 | 187 | res, err := ipfslog.NewFromMultihash(ctx, ipfs, identities[0], hash, &ipfslog.LogOptions{}, &ipfslog.FetchOptions{Length: intPtr(10)}) 188 | require.NoError(t, err) 189 | require.Equal(t, res.Entries.Len(), 10) 190 | }) 191 | 192 | t.Run("load only 10 entries and then expand to max from a log with 100 entries", func(t *testing.T) { 193 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 194 | require.NoError(t, err) 195 | log2, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "X"}) 196 | require.NoError(t, err) 197 | log3, err := ipfslog.NewLog(ipfs, identities[2], &ipfslog.LogOptions{ID: "X"}) 198 | require.NoError(t, err) 199 | 200 | for i := 0; i < 30; i++ { 201 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("hello%d", i)), nil) 202 | require.NoError(t, err) 203 | if i%10 == 0 { 204 | _, err := log2.Append(ctx, []byte(fmt.Sprintf("hi%d", i)), nil) 205 | require.NoError(t, err) 206 | _, err = log2.Join(log1, -1) 207 | require.NoError(t, err) 208 | } 209 | if i%25 == 0 { 210 | heads := append(entry.FindHeads(log3.Entries), entry.FindHeads(log2.Entries)...) 211 | log3, err = ipfslog.NewLog(ipfs, identities[2], &ipfslog.LogOptions{ID: log3.ID, Entries: log3.Values(), Heads: heads}) 212 | require.NoError(t, err) 213 | _, err := log3.Append(ctx, []byte(fmt.Sprintf("--%d", i)), nil) 214 | require.NoError(t, err) 215 | } 216 | } 217 | 218 | _, err = log3.Join(log2, -1) 219 | require.NoError(t, err) 220 | 221 | log4, err := ipfslog.NewLog(ipfs, identities[3], &ipfslog.LogOptions{ID: "X"}) 222 | require.NoError(t, err) 223 | _, err = log4.Join(log2, -1) 224 | require.NoError(t, err) 225 | _, err = log4.Join(log3, -1) 226 | require.NoError(t, err) 227 | 228 | var values3, values4 [][]byte 229 | log3Values := log3.Values() 230 | log3Keys := log3Values.Keys() 231 | 232 | log4Values := log4.Values() 233 | log4Keys := log4Values.Keys() 234 | 235 | for _, k := range log3Keys { 236 | v, _ := log3Values.Get(k) 237 | values3 = append(values3, v.GetPayload()) 238 | } 239 | for _, k := range log4Keys { 240 | v, _ := log4Values.Get(k) 241 | values4 = append(values4, v.GetPayload()) 242 | } 243 | require.Equal(t, values3, values4) 244 | }) 245 | } 246 | -------------------------------------------------------------------------------- /test/goleak_test.go: -------------------------------------------------------------------------------- 1 | //go:build goleak 2 | // +build goleak 3 | 4 | package test 5 | 6 | import ( 7 | "testing" 8 | 9 | "go.uber.org/goleak" 10 | ) 11 | 12 | // TestDoNothing is used to configure goleak based on leaks due to imports 13 | func TestDoNothing(t *testing.T) {} 14 | 15 | func TestMain(m *testing.M) { 16 | goleak.VerifyTestMain(m, 17 | goleak.IgnoreTopFunction("github.com/ipfs/go-log/writer.(*MirrorWriter).logRoutine"), // inherited from one of the imports (init) 18 | goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), // inherited from one of the imports (init) 19 | goleak.IgnoreTopFunction("github.com/libp2p/go-libp2p-connmgr.(*BasicConnMgr).background"), // inherited from github.com/ipfs/kubo/core.NewNode 20 | goleak.IgnoreTopFunction("github.com/jbenet/goprocess/periodic.callOnTicker.func1"), // inherited from github.com/ipfs/kubo/core.NewNode 21 | goleak.IgnoreTopFunction("github.com/libp2p/go-libp2p-connmgr.(*decayer).process"), // inherited from github.com/ipfs/kubo/core.NewNode 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /test/log_append_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "testing" 8 | 9 | "berty.tech/go-ipfs-log/enc" 10 | "berty.tech/go-ipfs-log/io/cbor" 11 | 12 | ipfslog "berty.tech/go-ipfs-log" 13 | "berty.tech/go-ipfs-log/entry" 14 | idp "berty.tech/go-ipfs-log/identityprovider" 15 | "berty.tech/go-ipfs-log/iface" 16 | "berty.tech/go-ipfs-log/keystore" 17 | dssync "github.com/ipfs/go-datastore/sync" 18 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 19 | "github.com/stretchr/testify/require" 20 | ) 21 | 22 | func TestLogAppend(t *testing.T) { 23 | ctx, cancel := context.WithCancel(context.Background()) 24 | defer cancel() 25 | 26 | m := mocknet.New() 27 | defer m.Close() 28 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 29 | defer closeNode() 30 | 31 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 32 | keystore, err := keystore.NewKeystore(datastore) 33 | require.NoError(t, err) 34 | 35 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 36 | Keystore: keystore, 37 | ID: fmt.Sprintf("userA"), 38 | Type: "orbitdb", 39 | }) 40 | require.NoError(t, err) 41 | 42 | t.Run("append one", func(t *testing.T) { 43 | log1, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A"}) 44 | require.NoError(t, err) 45 | _, err = log1.Append(ctx, []byte("hello1"), nil) 46 | require.NoError(t, err) 47 | 48 | require.Equal(t, log1.Entries.Len(), 1) 49 | values := log1.Values() 50 | keys := values.Keys() 51 | 52 | for _, k := range keys { 53 | v := values.UnsafeGet(k) 54 | require.Equal(t, string(v.GetPayload()), "hello1") 55 | require.Equal(t, len(v.GetNext()), 0) 56 | require.Equal(t, v.GetClock().GetID(), identity.PublicKey) 57 | require.Equal(t, v.GetClock().GetTime(), 1) 58 | } 59 | for _, v := range entry.FindHeads(log1.Entries) { 60 | require.Equal(t, v.GetHash().String(), values.UnsafeGet(keys[0]).GetHash().String()) 61 | } 62 | }) 63 | 64 | t.Run("append 100 items to a log", func(t *testing.T) { 65 | log1, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A"}) 66 | require.NoError(t, err) 67 | nextPointerAmount := 64 68 | 69 | for i := 0; i < 100; i++ { 70 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("hello%d", i)), &iface.AppendOptions{ 71 | PointerCount: nextPointerAmount, 72 | }) 73 | require.NoError(t, err) 74 | 75 | values := log1.Values() 76 | keys := values.Keys() 77 | heads := entry.FindHeads(log1.Entries) 78 | 79 | require.Equal(t, len(heads), 1) 80 | require.Equal(t, heads[0].GetHash().String(), values.UnsafeGet(keys[len(keys)-1]).GetHash().String()) 81 | } 82 | 83 | require.Equal(t, log1.Entries.Len(), 100) 84 | 85 | values := log1.Values() 86 | keys := values.Keys() 87 | 88 | for i, k := range keys { 89 | v := values.UnsafeGet(k) 90 | 91 | require.Equal(t, string(v.GetPayload()), fmt.Sprintf("hello%d", i)) 92 | require.Equal(t, v.GetClock().GetTime(), i+1) 93 | require.Equal(t, v.GetClock().GetID(), identity.PublicKey) 94 | 95 | if i == 0 { 96 | require.Equal(t, len(v.GetRefs()), 0) 97 | } else { 98 | expected := math.Ceil(math.Log2(math.Min(float64(nextPointerAmount), float64(i)))) 99 | 100 | require.Equal(t, len(v.GetRefs()), int(expected)) 101 | } 102 | } 103 | }) 104 | } 105 | 106 | func TestLogAppendEncrypted(t *testing.T) { 107 | ctx, cancel := context.WithCancel(context.Background()) 108 | defer cancel() 109 | 110 | m := mocknet.New() 111 | defer m.Close() 112 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 113 | defer closeNode() 114 | 115 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 116 | keystore, err := keystore.NewKeystore(datastore) 117 | require.NoError(t, err) 118 | 119 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 120 | Keystore: keystore, 121 | ID: fmt.Sprintf("userA"), 122 | Type: "orbitdb", 123 | }) 124 | require.NoError(t, err) 125 | 126 | cborioDefault, err := cbor.IO(&entry.Entry{}, &entry.LamportClock{}) 127 | require.NoError(t, err) 128 | 129 | logKey, err := enc.NewSecretbox([]byte{ 130 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 131 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 132 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 133 | 'a', 'b', 134 | }) 135 | require.NoError(t, err) 136 | 137 | logKeyDiff, err := enc.NewSecretbox([]byte{ 138 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 139 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 140 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 141 | 'a', 'c', 142 | }) 143 | require.NoError(t, err) 144 | 145 | cborioDiff := cborioDefault.ApplyOptions(&cbor.Options{LinkKey: logKeyDiff}) 146 | cborio := cborioDefault.ApplyOptions(&cbor.Options{LinkKey: logKey}) 147 | 148 | t.Run("NewFromEntryHash - succeed with same keys", func(t *testing.T) { 149 | l, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "X", IO: cborio}) 150 | require.NoError(t, err) 151 | 152 | _, err = l.Append(ctx, []byte("helloA1"), nil) 153 | require.NoError(t, err) 154 | 155 | _, err = l.Append(ctx, []byte("helloA2"), nil) 156 | require.NoError(t, err) 157 | 158 | _, err = l.Append(ctx, []byte("helloA3"), nil) 159 | require.NoError(t, err) 160 | 161 | h, err := l.Append(ctx, []byte("helloA4"), nil) 162 | require.NoError(t, err) 163 | 164 | l2, err := ipfslog.NewFromEntryHash(ctx, ipfs, identity, h.GetHash(), 165 | &ipfslog.LogOptions{ 166 | ID: "A", 167 | IO: cborio, 168 | }, &ipfslog.FetchOptions{}) 169 | require.NoError(t, err) 170 | 171 | require.Equal(t, 4, l.Values().Len()) 172 | //require.Equal(t, 4, l2.Values().Len()) 173 | 174 | expected := []string{"helloA1", "helloA2", "helloA3", "helloA4"} 175 | var result []string 176 | 177 | for _, v := range l2.Values().Keys() { 178 | result = append(result, string(l2.Values().UnsafeGet(v).GetPayload())) 179 | } 180 | 181 | require.Equal(t, expected, result) 182 | require.Equal(t, len(getLastEntry(l2.Values()).GetNext()), 1) 183 | }) 184 | 185 | t.Run("NewFromEntryHash - fails with diff keys", func(t *testing.T) { 186 | l, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "X", IO: cborio}) 187 | require.NoError(t, err) 188 | 189 | _, err = l.Append(ctx, []byte("helloA1"), nil) 190 | require.NoError(t, err) 191 | 192 | _, err = l.Append(ctx, []byte("helloA2"), nil) 193 | require.NoError(t, err) 194 | 195 | _, err = l.Append(ctx, []byte("helloA3"), nil) 196 | require.NoError(t, err) 197 | 198 | h, err := l.Append(ctx, []byte("helloA4"), nil) 199 | require.NoError(t, err) 200 | 201 | l2, err := ipfslog.NewFromEntryHash(ctx, ipfs, identity, h.GetHash(), 202 | &ipfslog.LogOptions{ 203 | ID: "A", 204 | IO: cborioDiff, 205 | }, &ipfslog.FetchOptions{}) 206 | require.NoError(t, err) 207 | 208 | require.Equal(t, 4, l.Values().Len()) 209 | require.Equal(t, 0, l2.Values().Len()) 210 | 211 | var result []string 212 | for _, v := range l2.Values().Keys() { 213 | result = append(result, string(l2.Values().UnsafeGet(v).GetPayload())) 214 | } 215 | 216 | require.Empty(t, result) 217 | }) 218 | 219 | t.Run("NewFromEntryHash - fails with no key", func(t *testing.T) { 220 | l, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "X", IO: cborio}) 221 | require.NoError(t, err) 222 | 223 | _, err = l.Append(ctx, []byte("helloA1"), nil) 224 | require.NoError(t, err) 225 | 226 | _, err = l.Append(ctx, []byte("helloA2"), nil) 227 | require.NoError(t, err) 228 | 229 | _, err = l.Append(ctx, []byte("helloA3"), nil) 230 | require.NoError(t, err) 231 | 232 | h, err := l.Append(ctx, []byte("helloA4"), nil) 233 | require.NoError(t, err) 234 | 235 | l2, err := ipfslog.NewFromEntryHash(ctx, ipfs, identity, h.GetHash(), 236 | &ipfslog.LogOptions{ 237 | ID: "A", 238 | IO: cborioDefault, 239 | }, &ipfslog.FetchOptions{}) 240 | require.NoError(t, err) 241 | 242 | require.Equal(t, 4, l.Values().Len()) 243 | require.Equal(t, 1, l2.Values().Len()) 244 | 245 | var result []string 246 | for _, v := range l2.Values().Keys() { 247 | result = append(result, string(l2.Values().UnsafeGet(v).GetPayload())) 248 | } 249 | 250 | require.Equal(t, result, []string{"helloA4"}) 251 | }) 252 | } 253 | -------------------------------------------------------------------------------- /test/log_heads_tails_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | ipfslog "berty.tech/go-ipfs-log" 9 | "berty.tech/go-ipfs-log/entry" 10 | idp "berty.tech/go-ipfs-log/identityprovider" 11 | ks "berty.tech/go-ipfs-log/keystore" 12 | ds "github.com/ipfs/go-datastore" 13 | dssync "github.com/ipfs/go-datastore/sync" 14 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func TestLogHeadsTails(t *testing.T) { 19 | ctx, cancel := context.WithCancel(context.Background()) 20 | defer cancel() 21 | 22 | m := mocknet.New() 23 | defer m.Close() 24 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 25 | defer closeNode() 26 | 27 | datastore := dssync.MutexWrap(ds.NewMapDatastore()) 28 | keystore, err := ks.NewKeystore(datastore) 29 | require.NoError(t, err) 30 | 31 | var identities []*idp.Identity 32 | 33 | for i := 0; i < 4; i++ { 34 | char := 'A' + i 35 | 36 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 37 | Keystore: keystore, 38 | ID: fmt.Sprintf("user%c", char), 39 | Type: "orbitdb", 40 | }) 41 | require.NoError(t, err) 42 | 43 | identities = append(identities, identity) 44 | } 45 | 46 | t.Run("heads", func(t *testing.T) { 47 | t.Run("finds one head after one entry", func(t *testing.T) { 48 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 49 | require.NoError(t, err) 50 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 51 | require.NoError(t, err) 52 | 53 | require.Equal(t, len(entry.FindHeads(log1.Entries)), 1) 54 | }) 55 | 56 | t.Run("finds one head after two entry", func(t *testing.T) { 57 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 58 | require.NoError(t, err) 59 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 60 | require.NoError(t, err) 61 | _, err = log1.Append(ctx, []byte("helloA2"), nil) 62 | require.NoError(t, err) 63 | 64 | require.Equal(t, len(entry.FindHeads(log1.Entries)), 1) 65 | }) 66 | 67 | t.Run("finds head after a join and append", func(t *testing.T) { 68 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 69 | require.NoError(t, err) 70 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 71 | require.NoError(t, err) 72 | 73 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 74 | require.NoError(t, err) 75 | _, err = log1.Append(ctx, []byte("helloA2"), nil) 76 | require.NoError(t, err) 77 | _, err = log2.Append(ctx, []byte("helloB1"), nil) 78 | require.NoError(t, err) 79 | 80 | _, err = log2.Join(log1, -1) 81 | require.NoError(t, err) 82 | _, err = log2.Append(ctx, []byte("helloB2"), nil) 83 | require.NoError(t, err) 84 | 85 | lastEntry := getLastEntry(log2.Values()) 86 | 87 | require.Equal(t, len(entry.FindHeads(log2.Entries)), 1) 88 | require.Equal(t, entry.FindHeads(log2.Entries)[0].GetHash().String(), lastEntry.GetHash().String()) 89 | }) 90 | 91 | t.Run("finds two heads after a join", func(t *testing.T) { 92 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 93 | require.NoError(t, err) 94 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 95 | require.NoError(t, err) 96 | 97 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 98 | require.NoError(t, err) 99 | _, err = log1.Append(ctx, []byte("helloA2"), nil) 100 | require.NoError(t, err) 101 | lastEntry1 := getLastEntry(log1.Values()) 102 | 103 | _, err = log2.Append(ctx, []byte("helloB1"), nil) 104 | require.NoError(t, err) 105 | _, err = log2.Append(ctx, []byte("helloB2"), nil) 106 | require.NoError(t, err) 107 | lastEntry2 := getLastEntry(log2.Values()) 108 | 109 | _, err = log1.Join(log2, -1) 110 | require.NoError(t, err) 111 | 112 | require.Equal(t, len(entry.FindHeads(log1.Entries)), 2) 113 | require.Equal(t, entry.FindHeads(log1.Entries)[0].GetHash().String(), lastEntry1.GetHash().String()) 114 | require.Equal(t, entry.FindHeads(log1.Entries)[1].GetHash().String(), lastEntry2.GetHash().String()) 115 | }) 116 | 117 | t.Run("finds two heads after two joins", func(t *testing.T) { 118 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 119 | require.NoError(t, err) 120 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 121 | require.NoError(t, err) 122 | 123 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 124 | require.NoError(t, err) 125 | _, err = log1.Append(ctx, []byte("helloA2"), nil) 126 | require.NoError(t, err) 127 | 128 | _, err = log2.Append(ctx, []byte("helloB1"), nil) 129 | require.NoError(t, err) 130 | _, err = log2.Append(ctx, []byte("helloB2"), nil) 131 | require.NoError(t, err) 132 | 133 | _, err = log1.Join(log2, -1) 134 | require.NoError(t, err) 135 | 136 | _, err = log2.Append(ctx, []byte("helloB3"), nil) 137 | require.NoError(t, err) 138 | 139 | _, err = log1.Append(ctx, []byte("helloA3"), nil) 140 | require.NoError(t, err) 141 | _, err = log1.Append(ctx, []byte("helloA4"), nil) 142 | require.NoError(t, err) 143 | 144 | lastEntry1 := getLastEntry(log1.Values()) 145 | lastEntry2 := getLastEntry(log2.Values()) 146 | 147 | _, err = log1.Join(log2, -1) 148 | require.NoError(t, err) 149 | 150 | require.Equal(t, len(entry.FindHeads(log1.Entries)), 2) 151 | require.Equal(t, entry.FindHeads(log1.Entries)[0].GetHash().String(), lastEntry1.GetHash().String()) 152 | require.Equal(t, entry.FindHeads(log1.Entries)[1].GetHash().String(), lastEntry2.GetHash().String()) 153 | }) 154 | 155 | t.Run("finds two heads after three joins", func(t *testing.T) { 156 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 157 | require.NoError(t, err) 158 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 159 | require.NoError(t, err) 160 | log3, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 161 | require.NoError(t, err) 162 | 163 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 164 | require.NoError(t, err) 165 | _, err = log1.Append(ctx, []byte("helloA2"), nil) 166 | require.NoError(t, err) 167 | _, err = log2.Append(ctx, []byte("helloB1"), nil) 168 | require.NoError(t, err) 169 | _, err = log2.Append(ctx, []byte("helloB2"), nil) 170 | require.NoError(t, err) 171 | _, err = log1.Join(log2, -1) 172 | require.NoError(t, err) 173 | _, err = log1.Append(ctx, []byte("helloA3"), nil) 174 | require.NoError(t, err) 175 | _, err = log1.Append(ctx, []byte("helloA4"), nil) 176 | require.NoError(t, err) 177 | lastEntry1 := getLastEntry(log1.Values()) 178 | _, err = log3.Append(ctx, []byte("helloC1"), nil) 179 | require.NoError(t, err) 180 | _, err = log3.Append(ctx, []byte("helloC2"), nil) 181 | require.NoError(t, err) 182 | _, err = log2.Join(log3, -1) 183 | require.NoError(t, err) 184 | _, err = log2.Append(ctx, []byte("helloB3"), nil) 185 | require.NoError(t, err) 186 | lastEntry2 := getLastEntry(log2.Values()) 187 | _, err = log1.Join(log2, -1) 188 | require.NoError(t, err) 189 | 190 | require.Equal(t, len(entry.FindHeads(log1.Entries)), 2) 191 | require.Equal(t, entry.FindHeads(log1.Entries)[0].GetHash().String(), lastEntry1.GetHash().String()) 192 | require.Equal(t, entry.FindHeads(log1.Entries)[1].GetHash().String(), lastEntry2.GetHash().String()) 193 | }) 194 | 195 | t.Run("finds three heads after three joins", func(t *testing.T) { 196 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 197 | require.NoError(t, err) 198 | log2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 199 | require.NoError(t, err) 200 | log3, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 201 | require.NoError(t, err) 202 | 203 | _, err = log1.Append(ctx, []byte("helloA1"), nil) 204 | require.NoError(t, err) 205 | _, err = log1.Append(ctx, []byte("helloA2"), nil) 206 | require.NoError(t, err) 207 | _, err = log2.Append(ctx, []byte("helloB1"), nil) 208 | require.NoError(t, err) 209 | _, err = log2.Append(ctx, []byte("helloB2"), nil) 210 | require.NoError(t, err) 211 | _, err = log1.Join(log2, -1) 212 | require.NoError(t, err) 213 | _, err = log1.Append(ctx, []byte("helloA3"), nil) 214 | require.NoError(t, err) 215 | _, err = log1.Append(ctx, []byte("helloA4"), nil) 216 | require.NoError(t, err) 217 | lastEntry1 := getLastEntry(log1.Values()) 218 | _, err = log3.Append(ctx, []byte("helloC1"), nil) 219 | require.NoError(t, err) 220 | _, err = log2.Append(ctx, []byte("helloB3"), nil) 221 | require.NoError(t, err) 222 | _, err = log3.Append(ctx, []byte("helloC2"), nil) 223 | require.NoError(t, err) 224 | lastEntry2 := getLastEntry(log2.Values()) 225 | lastEntry3 := getLastEntry(log3.Values()) 226 | _, err = log1.Join(log2, -1) 227 | require.NoError(t, err) 228 | _, err = log1.Join(log3, -1) 229 | require.NoError(t, err) 230 | 231 | require.Equal(t, len(entry.FindHeads(log1.Entries)), 3) 232 | require.Equal(t, entry.FindHeads(log1.Entries)[0].GetHash().String(), lastEntry1.GetHash().String()) 233 | require.Equal(t, entry.FindHeads(log1.Entries)[1].GetHash().String(), lastEntry2.GetHash().String()) 234 | require.Equal(t, entry.FindHeads(log1.Entries)[2].GetHash().String(), lastEntry3.GetHash().String()) 235 | }) 236 | }) 237 | 238 | t.Run("tails", func(t *testing.T) { 239 | // TODO: implements findTails(orderedmap) 240 | // t.Run("returns a tail", func(t *testing.T) { 241 | // log1, err := log.NewLog(ipfs, identities[0], &log.LogOptions{ID: "A"}) 242 | // require.NoError(t, err) 243 | // _, err = log1.Append([]byte("helloA1"), nil) 244 | // require.NoError(t, err) 245 | // require.Equal(t, len(log.FindTails(log1.Entries)), 1) 246 | // }) 247 | 248 | // t.Run("returns tail entries", func(t *testing.T) { 249 | // log1, err := log.NewLog(ipfs, identities[0], &log.LogOptions{ID: "A"}) 250 | // require.NoError(t, err) 251 | // log2, err := log.NewLog(ipfs, identities[0], &log.LogOptions{ID: "A"}) 252 | // require.NoError(t, err) 253 | // _, err = log1.Append([]byte("helloA1"), nil) 254 | // require.NoError(t, err) 255 | // _, err = log1.Append([]byte("helloA1"), nil) 256 | // require.NoError(t, err) 257 | // require.Equal(t, len(log.FindTails(log1.Entries)), 1) 258 | // }) 259 | }) 260 | } 261 | -------------------------------------------------------------------------------- /test/log_join_concurrent_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | ipfslog "berty.tech/go-ipfs-log" 9 | "berty.tech/go-ipfs-log/entry/sorting" 10 | idp "berty.tech/go-ipfs-log/identityprovider" 11 | ks "berty.tech/go-ipfs-log/keystore" 12 | dssync "github.com/ipfs/go-datastore/sync" 13 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestLogJoinConcurrent(t *testing.T) { 18 | ctx, cancel := context.WithCancel(context.Background()) 19 | defer cancel() 20 | 21 | m := mocknet.New() 22 | defer m.Close() 23 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 24 | defer closeNode() 25 | 26 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 27 | keystore, err := ks.NewKeystore(datastore) 28 | require.NoError(t, err) 29 | 30 | t.Run("join", func(t *testing.T) { 31 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 32 | Keystore: keystore, 33 | ID: "userA", 34 | Type: "orbitdb", 35 | }) 36 | require.NoError(t, err) 37 | 38 | log1, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A", SortFn: sorting.SortByEntryHash}) 39 | require.NoError(t, err) 40 | 41 | log2, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A", SortFn: sorting.SortByEntryHash}) 42 | require.NoError(t, err) 43 | 44 | // joins consistently 45 | for i := 0; i < 10; i++ { 46 | _, err = log1.Append(ctx, []byte(fmt.Sprintf("hello1-%d", i)), nil) 47 | require.NoError(t, err) 48 | 49 | _, err = log2.Append(ctx, []byte(fmt.Sprintf("hello2-%d", i)), nil) 50 | require.NoError(t, err) 51 | } 52 | 53 | _, err = log1.Join(log2, -1) 54 | require.NoError(t, err) 55 | 56 | _, err = log2.Join(log1, -1) 57 | require.NoError(t, err) 58 | 59 | hash1, err := log1.ToMultihash(ctx) 60 | require.NoError(t, err) 61 | 62 | hash2, err := log2.ToMultihash(ctx) 63 | require.NoError(t, err) 64 | 65 | require.True(t, hash1.Equals(hash2)) 66 | require.Equal(t, log1.Values().Len(), 20) 67 | require.Equal(t, log1.ToString(nil), log2.ToString(nil)) 68 | 69 | // Concurrently appending same payload after join results in same state 70 | for i := 10; i < 20; i++ { 71 | _, err = log1.Append(ctx, []byte(fmt.Sprintf("hello1-%d", i)), nil) 72 | require.NoError(t, err) 73 | 74 | _, err = log2.Append(ctx, []byte(fmt.Sprintf("hello2-%d", i)), nil) 75 | require.NoError(t, err) 76 | } 77 | 78 | _, err = log1.Join(log2, -1) 79 | require.NoError(t, err) 80 | 81 | _, err = log2.Join(log1, -1) 82 | require.NoError(t, err) 83 | 84 | _, err = log1.Append(ctx, []byte("same"), nil) 85 | require.NoError(t, err) 86 | 87 | _, err = log2.Append(ctx, []byte("same"), nil) 88 | require.NoError(t, err) 89 | 90 | hash1, err = log1.ToMultihash(ctx) 91 | require.NoError(t, err) 92 | 93 | hash2, err = log2.ToMultihash(ctx) 94 | require.NoError(t, err) 95 | 96 | require.True(t, hash1.Equals(hash2)) 97 | require.Equal(t, log1.Values().Len(), 41) 98 | require.Equal(t, log2.Values().Len(), 41) 99 | require.Equal(t, log1.ToString(nil), log2.ToString(nil)) 100 | 101 | // Joining after concurrently appending same payload joins entry once 102 | _, err = log1.Join(log2, -1) 103 | require.NoError(t, err) 104 | 105 | _, err = log2.Join(log1, -1) 106 | require.NoError(t, err) 107 | 108 | require.Equal(t, log1.Entries.Len(), log2.Entries.Len()) 109 | require.Equal(t, log1.Entries.Len(), 41) 110 | require.Equal(t, log1.ToString(nil), log2.ToString(nil)) 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /test/log_references_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "testing" 8 | 9 | ipfslog "berty.tech/go-ipfs-log" 10 | idp "berty.tech/go-ipfs-log/identityprovider" 11 | ks "berty.tech/go-ipfs-log/keystore" 12 | dssync "github.com/ipfs/go-datastore/sync" 13 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestLogReferences(t *testing.T) { 18 | ctx, cancel := context.WithCancel(context.Background()) 19 | defer cancel() 20 | 21 | m := mocknet.New() 22 | defer m.Close() 23 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 24 | defer closeNode() 25 | 26 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 27 | keystore, err := ks.NewKeystore(datastore) 28 | require.NoError(t, err) 29 | 30 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 31 | Keystore: keystore, 32 | ID: "userA", 33 | Type: "orbitdb", 34 | }) 35 | require.NoError(t, err) 36 | 37 | t.Run("creates entries with references", func(t *testing.T) { 38 | amount := 64 39 | maxReferenceDistance := 2 40 | 41 | log1, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A"}) 42 | require.NoError(t, err) 43 | 44 | log2, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "B"}) 45 | require.NoError(t, err) 46 | 47 | log3, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "C"}) 48 | require.NoError(t, err) 49 | 50 | log4, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "D"}) 51 | require.NoError(t, err) 52 | 53 | for i := 0; i < amount; i++ { 54 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("%d", i)), &ipfslog.AppendOptions{PointerCount: maxReferenceDistance}) 55 | require.NoError(t, err) 56 | } 57 | 58 | for i := 0; i < amount*2; i++ { 59 | pointerCount := math.Pow(float64(maxReferenceDistance), 2) 60 | 61 | _, err := log2.Append(ctx, []byte(fmt.Sprintf("%d", i)), &ipfslog.AppendOptions{PointerCount: int(pointerCount)}) 62 | require.NoError(t, err) 63 | } 64 | 65 | for i := 0; i < amount*3; i++ { 66 | pointerCount := math.Pow(float64(maxReferenceDistance), 3) 67 | 68 | _, err := log3.Append(ctx, []byte(fmt.Sprintf("%d", i)), &ipfslog.AppendOptions{PointerCount: int(pointerCount)}) 69 | require.NoError(t, err) 70 | } 71 | 72 | for i := 0; i < amount*4; i++ { 73 | pointerCount := math.Pow(float64(maxReferenceDistance), 4) 74 | 75 | _, err := log4.Append(ctx, []byte(fmt.Sprintf("%d", i)), &ipfslog.AppendOptions{PointerCount: int(pointerCount)}) 76 | require.NoError(t, err) 77 | } 78 | 79 | require.NotNil(t, log1.Entries.At(uint(log1.Entries.Len()-1))) 80 | require.Equal(t, len(log1.Entries.At(uint(log1.Entries.Len()-1)).GetNext()), 1) 81 | 82 | require.NotNil(t, log2.Entries.At(uint(log2.Entries.Len()-1))) 83 | require.Equal(t, len(log2.Entries.At(uint(log2.Entries.Len()-1)).GetNext()), 1) 84 | 85 | require.NotNil(t, log3.Entries.At(uint(log3.Entries.Len()-1))) 86 | require.Equal(t, len(log3.Entries.At(uint(log3.Entries.Len()-1)).GetNext()), 1) 87 | 88 | require.NotNil(t, log4.Entries.At(uint(log4.Entries.Len()-1))) 89 | require.Equal(t, len(log4.Entries.At(uint(log4.Entries.Len()-1)).GetNext()), 1) 90 | 91 | require.NotNil(t, log1.Entries.At(uint(log1.Entries.Len()-1))) 92 | require.Equal(t, len(log1.Entries.At(uint(log1.Entries.Len()-1)).GetRefs()), 1) 93 | 94 | require.NotNil(t, log2.Entries.At(uint(log2.Entries.Len()-1))) 95 | require.Equal(t, len(log2.Entries.At(uint(log2.Entries.Len()-1)).GetRefs()), 2) 96 | 97 | require.NotNil(t, log3.Entries.At(uint(log3.Entries.Len()-1))) 98 | require.Equal(t, len(log3.Entries.At(uint(log3.Entries.Len()-1)).GetRefs()), 3) 99 | 100 | require.NotNil(t, log4.Entries.At(uint(log4.Entries.Len()-1))) 101 | require.Equal(t, len(log4.Entries.At(uint(log4.Entries.Len()-1)).GetRefs()), 4) 102 | }) 103 | } 104 | 105 | func TestLogReferences2(t *testing.T) { 106 | ctx, cancel := context.WithCancel(context.Background()) 107 | defer cancel() 108 | 109 | m := mocknet.New() 110 | defer m.Close() 111 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 112 | defer closeNode() 113 | 114 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 115 | keystore, err := ks.NewKeystore(datastore) 116 | require.NoError(t, err) 117 | 118 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 119 | Keystore: keystore, 120 | ID: "userA", 121 | Type: "orbitdb", 122 | }) 123 | require.NoError(t, err) 124 | 125 | for _, input := range []struct { 126 | amount int 127 | referenceCount int 128 | refLength int 129 | }{ 130 | {1, 1, 0}, 131 | {1, 2, 0}, 132 | {2, 1, 1}, 133 | {2, 2, 1}, 134 | {3, 2, 1}, 135 | {3, 4, 1}, 136 | {4, 4, 2}, 137 | {4, 4, 2}, 138 | {32, 4, 2}, 139 | {32, 8, 3}, 140 | {32, 16, 4}, 141 | {18, 32, 5}, 142 | {128, 32, 5}, 143 | {64, 64, 6}, 144 | {65, 64, 6}, 145 | {128, 64, 6}, 146 | {128, 1, 0}, 147 | {128, 2, 1}, 148 | {256, 1, 0}, 149 | {256, 256, 8}, 150 | {256, 1024, 8}, 151 | } { 152 | key := fmt.Sprintf("has %d references, max distance %d, total of %d entries", input.refLength, input.referenceCount, input.amount) 153 | t.Run(key, func(t *testing.T) { 154 | log1, err := ipfslog.NewLog(ipfs, identity, &ipfslog.LogOptions{ID: "A"}) 155 | require.NoError(t, err) 156 | 157 | for i := 0; i < input.amount; i++ { 158 | _, err := log1.Append(ctx, []byte(fmt.Sprintf("%d", i+1)), &ipfslog.AppendOptions{PointerCount: input.referenceCount}) 159 | require.NoError(t, err) 160 | } 161 | require.Equal(t, log1.Entries.Len(), input.amount) 162 | require.Equal(t, log1.Entries.At(uint(input.amount-1)).GetClock().GetTime(), input.amount) 163 | 164 | for k := 0; k < input.amount; k++ { 165 | idx := log1.Entries.Len() - k - 1 166 | 167 | atIdx := log1.Entries.At(uint(idx)) 168 | require.NotNilf(t, atIdx, "idx=%d", idx) 169 | require.Equal(t, atIdx.GetClock().GetTime(), idx+1) 170 | 171 | // Check the first ref (distance 2) 172 | if len(atIdx.GetRefs()) > 0 { 173 | otherAt := log1.Entries.At(uint(idx - 2)) 174 | require.NotNilf(t, otherAt, "index=%d", idx-2) 175 | require.True(t, atIdx.GetRefs()[0].Equals(otherAt.GetHash())) 176 | } 177 | 178 | // Check the second ref (distance 2) 179 | if len(atIdx.GetRefs()) > 1 && idx > input.referenceCount { 180 | otherAt := log1.Entries.At(uint(idx - 4)) 181 | require.NotNilf(t, otherAt, "index=%d", idx-4) 182 | require.True(t, atIdx.GetRefs()[1].Equals(otherAt.GetHash())) 183 | } 184 | 185 | // Check the third ref (distance 4) 186 | if len(atIdx.GetRefs()) > 2 && idx > input.referenceCount { 187 | otherAt := log1.Entries.At(uint(idx - 8)) 188 | require.NotNilf(t, otherAt, "index=%d", idx-8) 189 | require.True(t, atIdx.GetRefs()[2].Equals(otherAt.GetHash())) 190 | } 191 | 192 | // Check the fourth ref (distance 8) 193 | if len(atIdx.GetRefs()) > 3 && idx > input.referenceCount { 194 | otherAt := log1.Entries.At(uint(idx - 16)) 195 | require.NotNilf(t, otherAt, "index=%d", idx-16) 196 | require.True(t, atIdx.GetRefs()[3].Equals(otherAt.GetHash())) 197 | } 198 | 199 | // Check the fifth ref (distance 16) 200 | if len(atIdx.GetRefs()) > 4 && idx > input.referenceCount { 201 | otherAt := log1.Entries.At(uint(idx - 32)) 202 | require.NotNilf(t, otherAt, "index=%d", idx-32) 203 | require.True(t, atIdx.GetRefs()[4].Equals(otherAt.GetHash())) 204 | } 205 | 206 | // Check the reference of each entry 207 | if idx > input.referenceCount { 208 | require.Equal(t, len(atIdx.GetRefs()), input.refLength) 209 | } 210 | } 211 | }) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /test/log_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | ipfslog "berty.tech/go-ipfs-log" 11 | "berty.tech/go-ipfs-log/entry" 12 | "berty.tech/go-ipfs-log/errmsg" 13 | idp "berty.tech/go-ipfs-log/identityprovider" 14 | "berty.tech/go-ipfs-log/iface" 15 | ks "berty.tech/go-ipfs-log/keystore" 16 | dssync "github.com/ipfs/go-datastore/sync" 17 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | func TestLog(t *testing.T) { 22 | ctx, cancel := context.WithCancel(context.Background()) 23 | defer cancel() 24 | 25 | m := mocknet.New() 26 | defer m.Close() 27 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 28 | defer closeNode() 29 | 30 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 31 | keystore, err := ks.NewKeystore(datastore) 32 | require.NoError(t, err) 33 | 34 | var identities []*idp.Identity 35 | 36 | for i := 0; i < 4; i++ { 37 | char := 'A' + i 38 | 39 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 40 | Keystore: keystore, 41 | ID: fmt.Sprintf("user%c", char), 42 | Type: "orbitdb", 43 | }) 44 | require.NoError(t, err) 45 | 46 | identities = append(identities, identity) 47 | } 48 | 49 | t.Run("sets an id and a clock id", func(t *testing.T) { 50 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 51 | require.NoError(t, err) 52 | require.Equal(t, log1.ID, "A") 53 | require.Equal(t, log1.Clock.GetID(), identities[0].PublicKey) 54 | }) 55 | 56 | t.Run("sets time.now as id string if id is not passed as an argument", func(t *testing.T) { 57 | before := time.Now().Unix() / 1000 58 | log1, err := ipfslog.NewLog(ipfs, identities[0], nil) 59 | require.NoError(t, err) 60 | after := time.Now().Unix() / 1000 61 | 62 | logid, err := strconv.ParseInt(log1.ID, 10, 64) 63 | require.NoError(t, err) 64 | 65 | require.GreaterOrEqual(t, logid, before) 66 | require.LessOrEqual(t, logid, after) 67 | }) 68 | 69 | t.Run("sets items if given as params", func(t *testing.T) { 70 | id1, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 71 | Keystore: keystore, 72 | ID: "userA", 73 | Type: "orbitdb", 74 | }) 75 | require.NoError(t, err) 76 | 77 | id2, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 78 | Keystore: keystore, 79 | ID: "userB", 80 | Type: "orbitdb", 81 | }) 82 | require.NoError(t, err) 83 | 84 | id3, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 85 | Keystore: keystore, 86 | ID: "userC", 87 | Type: "orbitdb", 88 | }) 89 | require.NoError(t, err) 90 | 91 | e1, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryA"), LogID: "A", Clock: entry.NewLamportClock(id1.PublicKey, 0)}, nil) 92 | require.NoError(t, err) 93 | 94 | e2, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryB"), LogID: "A", Clock: entry.NewLamportClock(id2.PublicKey, 1)}, nil) 95 | require.NoError(t, err) 96 | 97 | e3, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryC"), LogID: "A", Clock: entry.NewLamportClock(id3.PublicKey, 2)}, nil) 98 | require.NoError(t, err) 99 | 100 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A", Entries: entry.NewOrderedMapFromEntries([]iface.IPFSLogEntry{e1, e2, e3})}) 101 | require.NoError(t, err) 102 | 103 | values := log1.Values() 104 | 105 | require.Equal(t, values.Len(), 3) 106 | 107 | keys := values.Keys() 108 | require.Equal(t, string(values.UnsafeGet(keys[0]).GetPayload()), "entryA") 109 | require.Equal(t, string(values.UnsafeGet(keys[1]).GetPayload()), "entryB") 110 | require.Equal(t, string(values.UnsafeGet(keys[2]).GetPayload()), "entryC") 111 | }) 112 | 113 | t.Run("sets heads if given as params", func(t *testing.T) { 114 | e1, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryA"), LogID: "A"}, nil) 115 | require.NoError(t, err) 116 | 117 | e2, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryB"), LogID: "A"}, nil) 118 | require.NoError(t, err) 119 | 120 | e3, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryC"), LogID: "A"}, nil) 121 | require.NoError(t, err) 122 | 123 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "B", Entries: entry.NewOrderedMapFromEntries([]iface.IPFSLogEntry{e1, e2, e3}), Heads: []iface.IPFSLogEntry{e3}}) 124 | require.NoError(t, err) 125 | 126 | heads := log1.Heads() 127 | require.Equal(t, heads.Len(), 1) 128 | 129 | headsKeys := heads.Keys() 130 | require.Equal(t, heads.UnsafeGet(headsKeys[0]).GetHash().String(), e3.GetHash().String()) 131 | }) 132 | 133 | t.Run("finds heads if heads not given as params", func(t *testing.T) { 134 | e1, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryA"), LogID: "A"}, nil) 135 | require.NoError(t, err) 136 | 137 | e2, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryB"), LogID: "A"}, nil) 138 | require.NoError(t, err) 139 | 140 | e3, err := entry.CreateEntry(ctx, ipfs, identities[0], &entry.Entry{Payload: []byte("entryC"), LogID: "A"}, nil) 141 | require.NoError(t, err) 142 | 143 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A", Entries: entry.NewOrderedMapFromEntries([]iface.IPFSLogEntry{e1, e2, e3})}) 144 | require.NoError(t, err) 145 | 146 | heads := log1.Heads() 147 | require.Equal(t, heads.Len(), 3) 148 | 149 | headsKeys := heads.Keys() 150 | require.Equal(t, heads.UnsafeGet(headsKeys[2]).GetHash().String(), e1.GetHash().String()) 151 | require.Equal(t, heads.UnsafeGet(headsKeys[1]).GetHash().String(), e2.GetHash().String()) 152 | require.Equal(t, heads.UnsafeGet(headsKeys[0]).GetHash().String(), e3.GetHash().String()) 153 | }) 154 | 155 | t.Run("creates default public AccessController if not defined", func(t *testing.T) { 156 | log1, err := ipfslog.NewLog(ipfs, identities[0], nil) 157 | require.NoError(t, err) 158 | 159 | err = log1.AccessController.CanAppend(&entry.Entry{Payload: []byte("any")}, identities[0].Provider, nil) 160 | require.NoError(t, err) 161 | }) 162 | 163 | t.Run("returns an error if ipfs is not net", func(t *testing.T) { 164 | log1, err := ipfslog.NewLog(nil, identities[0], nil) 165 | require.Nil(t, log1) 166 | require.Equal(t, err, errmsg.ErrIPFSNotDefined) 167 | }) 168 | 169 | t.Run("returns an error if identity is not net", func(t *testing.T) { 170 | log1, err := ipfslog.NewLog(ipfs, nil, nil) 171 | require.Nil(t, log1) 172 | require.Equal(t, err, errmsg.ErrIdentityNotDefined) 173 | }) 174 | 175 | t.Run("toString", func(t *testing.T) { 176 | expectedData := "five\n└─four\n └─three\n └─two\n └─one" 177 | log1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 178 | require.NoError(t, err) 179 | for _, val := range []string{"one", "two", "three", "four", "five"} { 180 | _, err := log1.Append(ctx, []byte(val), nil) 181 | require.NoError(t, err) 182 | } 183 | 184 | require.Equal(t, log1.ToString(nil), expectedData) 185 | }) 186 | } 187 | -------------------------------------------------------------------------------- /test/signed_log_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | ipfslog "berty.tech/go-ipfs-log" 9 | "berty.tech/go-ipfs-log/accesscontroller" 10 | "berty.tech/go-ipfs-log/errmsg" 11 | idp "berty.tech/go-ipfs-log/identityprovider" 12 | ks "berty.tech/go-ipfs-log/keystore" 13 | dssync "github.com/ipfs/go-datastore/sync" 14 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | type DenyAll struct { 19 | } 20 | 21 | func (*DenyAll) CanAppend(accesscontroller.LogEntry, idp.Interface, accesscontroller.CanAppendAdditionalContext) error { 22 | return fmt.Errorf("denied") 23 | } 24 | 25 | type TestACL struct { 26 | refIdentity *idp.Identity 27 | } 28 | 29 | func (t *TestACL) CanAppend(e accesscontroller.LogEntry, p idp.Interface, _ accesscontroller.CanAppendAdditionalContext) error { 30 | if e.GetIdentity().ID == t.refIdentity.ID { 31 | return fmt.Errorf("denied") 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func TestSignedLog(t *testing.T) { 38 | ctx, cancel := context.WithCancel(context.Background()) 39 | defer cancel() 40 | 41 | m := mocknet.New() 42 | defer m.Close() 43 | ipfs, closeNode := NewMemoryServices(ctx, t, m) 44 | defer closeNode() 45 | 46 | datastore := dssync.MutexWrap(NewIdentityDataStore(t)) 47 | keystore, err := ks.NewKeystore(datastore) 48 | require.NoError(t, err) 49 | 50 | var identities [4]*idp.Identity 51 | 52 | for i, char := range []rune{'A', 'B'} { 53 | 54 | identity, err := idp.CreateIdentity(ctx, &idp.CreateIdentityOptions{ 55 | Keystore: keystore, 56 | ID: fmt.Sprintf("user%c", char), 57 | Type: "orbitdb", 58 | }) 59 | require.NoError(t, err) 60 | 61 | identities[i] = identity 62 | } 63 | 64 | t.Run("creates a signed log", func(t *testing.T) { 65 | logID := "A" 66 | l, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: logID}) 67 | require.NoError(t, err) 68 | require.NotNil(t, l.ID) 69 | require.Equal(t, l.ID, logID) 70 | }) 71 | 72 | t.Run("has the correct identity", func(t *testing.T) { 73 | l, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 74 | require.NoError(t, err) 75 | require.NotNil(t, l.ID) 76 | require.Equal(t, l.Identity.ID, "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c") 77 | require.Equal(t, l.Identity.PublicKey, MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361")) 78 | require.Equal(t, l.Identity.Signatures.ID, MustBytesFromHex(t, "3045022100f5f6f10571d14347aaf34e526ce3419fd64d75ffa7aa73692cbb6aeb6fbc147102203a3e3fa41fa8fcbb9fc7c148af5b640e2f704b20b3a4e0b93fc3a6d44dffb41e")) 79 | require.Equal(t, l.Identity.Signatures.PublicKey, MustBytesFromHex(t, "3044022020982b8492be0c184dc29de0a3a3bd86a86ba997756b0bf41ddabd24b47c5acf02203745fda39d7df650a5a478e52bbe879f0cb45c074025a93471414a56077640a4")) 80 | }) 81 | 82 | t.Run("has the correct public key", func(t *testing.T) { 83 | l, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 84 | require.NoError(t, err) 85 | require.NotNil(t, l.ID) 86 | require.Equal(t, l.Identity.PublicKey, identities[0].PublicKey) 87 | }) 88 | 89 | t.Run("has the correct pkSignature", func(t *testing.T) { 90 | l, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 91 | require.NoError(t, err) 92 | require.NotNil(t, l.ID) 93 | require.Equal(t, l.Identity.Signatures.ID, identities[0].Signatures.ID) 94 | }) 95 | 96 | t.Run("has the correct signature", func(t *testing.T) { 97 | l, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 98 | require.NoError(t, err) 99 | require.NotNil(t, l.ID) 100 | require.Equal(t, l.Identity.Signatures.PublicKey, identities[0].Signatures.PublicKey) 101 | }) 102 | 103 | ////////////// 104 | 105 | t.Run("entries contain an identity", func(t *testing.T) { 106 | l, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 107 | require.NoError(t, err) 108 | require.NotNil(t, l.ID) 109 | 110 | _, err = l.Append(ctx, []byte("one"), nil) 111 | require.NoError(t, err) 112 | require.NotNil(t, l.ID) 113 | 114 | require.NotNil(t, l.Values().At(0).GetSig()) 115 | require.Equal(t, l.Values().At(0).GetIdentity().Filtered(), identities[0].Filtered()) 116 | }) 117 | 118 | t.Run("doesn't sign entries when identity is not defined", func(t *testing.T) { 119 | _, err := ipfslog.NewLog(ipfs, nil, nil) 120 | require.Error(t, err) 121 | require.Equal(t, err, errmsg.ErrIdentityNotDefined) 122 | }) 123 | 124 | t.Run("doesn't join logs with different IDs", func(t *testing.T) { 125 | l1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 126 | require.NoError(t, err) 127 | require.NotNil(t, l1.ID) 128 | 129 | l2, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "B"}) 130 | require.NoError(t, err) 131 | require.NotNil(t, l2.ID) 132 | 133 | _, err = l1.Append(ctx, []byte("one"), nil) 134 | require.NoError(t, err) 135 | 136 | _, err = l2.Append(ctx, []byte("two"), nil) 137 | require.NoError(t, err) 138 | 139 | _, err = l2.Append(ctx, []byte("three"), nil) 140 | require.NoError(t, err) 141 | 142 | _, err = l1.Join(l2, -1) 143 | require.NoError(t, err) 144 | 145 | require.Equal(t, l1.ID, "A") 146 | require.Equal(t, l1.Values().Len(), 1) 147 | require.Equal(t, l1.Values().At(0).GetPayload(), []byte("one")) 148 | }) 149 | 150 | t.Run("throws an error if log is signed but trying to merge with an entry that doesn't have public signing key", func(t *testing.T) { 151 | l1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 152 | require.NoError(t, err) 153 | require.NotNil(t, l1.ID) 154 | 155 | l2, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "A"}) 156 | require.NoError(t, err) 157 | require.NotNil(t, l2.ID) 158 | 159 | _, err = l1.Append(ctx, []byte("one"), nil) 160 | require.NoError(t, err) 161 | 162 | _, err = l2.Append(ctx, []byte("two"), nil) 163 | require.NoError(t, err) 164 | 165 | l2.Values().At(0).SetKey(nil) 166 | 167 | _, err = l1.Join(l2, -1) 168 | require.Error(t, err) 169 | require.Contains(t, err.Error(), errmsg.ErrKeyNotDefined.Error()) 170 | }) 171 | 172 | t.Run("throws an error if log is signed but trying to merge an entry that doesn't have a signature", func(t *testing.T) { 173 | l1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 174 | require.NoError(t, err) 175 | require.NotNil(t, l1.ID) 176 | 177 | l2, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "A"}) 178 | require.NoError(t, err) 179 | require.NotNil(t, l2.ID) 180 | 181 | _, err = l1.Append(ctx, []byte("one"), nil) 182 | require.NoError(t, err) 183 | 184 | _, err = l2.Append(ctx, []byte("two"), nil) 185 | require.NoError(t, err) 186 | 187 | l2.Values().At(0).SetSig(nil) 188 | 189 | _, err = l1.Join(l2, -1) 190 | require.Error(t, err) 191 | require.Contains(t, err.Error(), errmsg.ErrSigNotDefined.Error()) 192 | }) 193 | 194 | t.Run("throws an error if log is signed but the signature doesn't verify", func(t *testing.T) { 195 | l1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 196 | require.NoError(t, err) 197 | require.NotNil(t, l1.ID) 198 | 199 | l2, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "A"}) 200 | require.NoError(t, err) 201 | require.NotNil(t, l2.ID) 202 | 203 | _, err = l1.Append(ctx, []byte("one"), nil) 204 | require.NoError(t, err) 205 | 206 | _, err = l2.Append(ctx, []byte("two"), nil) 207 | require.NoError(t, err) 208 | 209 | l2.Values().At(0).SetSig(l1.Values().At(0).GetSig()) 210 | 211 | _, err = l1.Join(l2, -1) 212 | require.Error(t, err) 213 | require.Contains(t, err.Error(), errmsg.ErrSigNotVerified.Error()) 214 | 215 | require.Equal(t, l1.Values().Len(), 1) 216 | require.Equal(t, l1.Values().At(0).GetPayload(), []byte("one")) 217 | }) 218 | 219 | t.Run("throws an error if entry doesn't have append access", func(t *testing.T) { 220 | l1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A"}) 221 | require.NoError(t, err) 222 | require.NotNil(t, l1.ID) 223 | 224 | l2, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "A", AccessController: &DenyAll{}}) 225 | require.NoError(t, err) 226 | require.NotNil(t, l2.ID) 227 | 228 | _, err = l1.Append(ctx, []byte("one"), nil) 229 | require.NoError(t, err) 230 | 231 | _, err = l2.Append(ctx, []byte("two"), nil) 232 | require.Error(t, err) 233 | require.Contains(t, err.Error(), errmsg.ErrLogAppendDenied.Error()) 234 | }) 235 | 236 | t.Run("throws an error upon join if entry doesn't have append access", func(t *testing.T) { 237 | l1, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "A", AccessController: &TestACL{refIdentity: identities[1]}}) 238 | require.NoError(t, err) 239 | require.NotNil(t, l1.ID) 240 | 241 | l2, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "A"}) 242 | require.NoError(t, err) 243 | require.NotNil(t, l2.ID) 244 | 245 | _, err = l1.Append(ctx, []byte("one"), nil) 246 | require.NoError(t, err) 247 | 248 | _, err = l2.Append(ctx, []byte("two"), nil) 249 | require.NoError(t, err) 250 | 251 | _, err = l1.Join(l2, -1) 252 | require.Error(t, err) 253 | require.Contains(t, err.Error(), errmsg.ErrLogJoinFailed.Error()) 254 | }) 255 | } 256 | -------------------------------------------------------------------------------- /test/utils.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "testing" 7 | 8 | cid "github.com/ipfs/go-cid" 9 | ds "github.com/ipfs/go-datastore" 10 | multibase "github.com/multiformats/go-multibase" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func NewIdentityDataStore(t testing.TB) ds.Datastore { 15 | t.Helper() 16 | 17 | var identityKeys = map[string][]byte{ 18 | "userA": MustBytesFromHex(t, "0a135ce157a9ccb8375c2fae0d472f1eade4b40b37704c02df923b78ca03c627"), 19 | "userB": MustBytesFromHex(t, "855f70d3b5224e5af76c23db0792339ca8d968a5a802ff0c5b54d674ef01aaad"), 20 | "userC": MustBytesFromHex(t, "291d4dc915d81e9ebe5627c3f5e7309e819e721ee75e63286baa913497d61c78"), 21 | "userD": MustBytesFromHex(t, "faa2d697318a6f8daeb8f4189fc657e7ae1b24e18c91c3bb9b95ad3c0cc050f8"), 22 | "02a38336e3a47f545a172c9f77674525471ebeda7d6c86140e7a778f67ded92260": MustBytesFromHex(t, "7c6140e9ae4c70eb11600b3d550cc6aac45511b5a660f4e75fe9a7c4e6d1c7b7"), 23 | "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c": MustBytesFromHex(t, "97f64ca2bf7bd6aa2136eb0aa3ce512433bd903b91d48b2208052d6ff286d080"), 24 | "032f7b6ef0432b572b45fcaf27e7f6757cd4123ff5c5266365bec82129b8c5f214": MustBytesFromHex(t, "2b487a932233c8691024c951faaeac207be161797bdda7bd934c0125012a5551"), 25 | "0358df8eb5def772917748fdf8a8b146581ad2041eae48d66cc6865f11783499a6": MustBytesFromHex(t, "1cd65d23d72932f5ca2328988d19a5b11fbab1f4c921ef2471768f1773bd56de"), 26 | } 27 | 28 | dataStore := ds.NewMapDatastore() 29 | for k, v := range identityKeys { 30 | err := dataStore.Put(context.Background(), ds.NewKey(k), v) 31 | require.NoError(t, err) 32 | } 33 | 34 | return dataStore 35 | } 36 | 37 | func CidB32(t testing.TB, b58CID string) string { 38 | t.Helper() 39 | 40 | c, err := cid.Parse(b58CID) 41 | require.NoError(t, err) 42 | 43 | return c.Encode(multibase.MustNewEncoder(multibase.Base32)) 44 | } 45 | 46 | func MustCID(t testing.TB, s string) cid.Cid { 47 | t.Helper() 48 | 49 | c, err := cid.Decode(s) 50 | require.NoError(t, err) 51 | 52 | return c 53 | } 54 | func MustBytesFromHex(t testing.TB, s string) []byte { 55 | t.Helper() 56 | 57 | b, err := hex.DecodeString(s) 58 | require.NoError(t, err) 59 | 60 | return b 61 | } 62 | -------------------------------------------------------------------------------- /test/utils_fixtures_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ipfs/go-cid" 7 | 8 | "berty.tech/go-ipfs-log/entry" 9 | "berty.tech/go-ipfs-log/identityprovider" 10 | ) 11 | 12 | func getEntriesV0Fixtures(t *testing.T) map[string]*entry.Entry { 13 | return map[string]*entry.Entry{ 14 | "hello": { 15 | Hash: MustCID(t, "Qmc2DEiLirMH73kHpuFPbt3V65sBrnDWkJYSjUQHXXvghT"), 16 | LogID: "A", 17 | Payload: []byte("hello"), 18 | V: 0, 19 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "0411a0d38181c9374eca3e480ecada96b1a4db9375c5e08c3991557759d22f6f2f902d0dc5364a948035002504d825308b0c257b7cbb35229c2076532531f8f4ef"), 0), 20 | Sig: MustBytesFromHex(t, "3044022062f4cfc8b8f3cc01283b25eab3eeb295614bb0faa8bd20f026c1487ae663121102207ce415bd7423b66d695338c17122e937259f77d1e86494d3146436f0959fccc6"), 21 | Key: MustBytesFromHex(t, "0411a0d38181c9374eca3e480ecada96b1a4db9375c5e08c3991557759d22f6f2f902d0dc5364a948035002504d825308b0c257b7cbb35229c2076532531f8f4ef"), 22 | Next: []cid.Cid{}, 23 | }, 24 | "helloWorld": { 25 | Hash: MustCID(t, "QmUKMoRrmsYAzQg1nQiD7Fzgpo24zXky7jVJNcZGiSAdhc"), 26 | LogID: "A", 27 | Payload: []byte("hello world"), 28 | V: 0, 29 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "0411a0d38181c9374eca3e480ecada96b1a4db9375c5e08c3991557759d22f6f2f902d0dc5364a948035002504d825308b0c257b7cbb35229c2076532531f8f4ef"), 0), 30 | Sig: MustBytesFromHex(t, "3044022062f4cfc8b8f3cc01283b25eab3eeb295614bb0faa8bd20f026c1487ae663121102207ce415bd7423b66d695338c17122e937259f77d1e86494d3146436f0959fccc6"), 31 | Key: MustBytesFromHex(t, "0411a0d38181c9374eca3e480ecada96b1a4db9375c5e08c3991557759d22f6f2f902d0dc5364a948035002504d825308b0c257b7cbb35229c2076532531f8f4ef"), 32 | Next: []cid.Cid{}, 33 | }, 34 | "helloAgain": { 35 | Hash: MustCID(t, "QmZ8va2fSjRufV1sD6x5mwi6E5GrSjXHx7RiKFVBzkiUNZ"), 36 | LogID: "A", 37 | Payload: []byte("hello again"), 38 | V: 0, 39 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "0411a0d38181c9374eca3e480ecada96b1a4db9375c5e08c3991557759d22f6f2f902d0dc5364a948035002504d825308b0c257b7cbb35229c2076532531f8f4ef"), 0), 40 | Sig: MustBytesFromHex(t, "3044022062f4cfc8b8f3cc01283b25eab3eeb295614bb0faa8bd20f026c1487ae663121102207ce415bd7423b66d695338c17122e937259f77d1e86494d3146436f0959fccc6"), 41 | Key: MustBytesFromHex(t, "0411a0d38181c9374eca3e480ecada96b1a4db9375c5e08c3991557759d22f6f2f902d0dc5364a948035002504d825308b0c257b7cbb35229c2076532531f8f4ef"), 42 | Next: []cid.Cid{MustCID(t, "QmUKMoRrmsYAzQg1nQiD7Fzgpo24zXky7jVJNcZGiSAdhc")}, 43 | }, 44 | } 45 | } 46 | 47 | func getEntriesV1Fixtures(t *testing.T, id *identityprovider.Identity) []entry.Entry { 48 | return []entry.Entry{ 49 | { 50 | Payload: []byte("one"), 51 | LogID: "A", 52 | Next: []cid.Cid{}, 53 | V: 1, 54 | Key: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 55 | Sig: MustBytesFromHex(t, "3045022100f72546c99cf30eda1d394d91209bdb4569408a792caf9dc7c6415fef37a3118d0220645c4a6d218f8fc478af5bab175aaa99e1505d70c2a00997aacafa8de697944e"), 56 | Identity: &identityprovider.Identity{ 57 | ID: "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c", 58 | PublicKey: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 59 | Signatures: &identityprovider.IdentitySignature{ 60 | ID: MustBytesFromHex(t, "3045022100f5f6f10571d14347aaf34e526ce3419fd64d75ffa7aa73692cbb6aeb6fbc147102203a3e3fa41fa8fcbb9fc7c148af5b640e2f704b20b3a4e0b93fc3a6d44dffb41e"), 61 | PublicKey: MustBytesFromHex(t, "3044022020982b8492be0c184dc29de0a3a3bd86a86ba997756b0bf41ddabd24b47c5acf02203745fda39d7df650a5a478e52bbe879f0cb45c074025a93471414a56077640a4"), 62 | }, 63 | Type: "orbitdb", 64 | Provider: id.Provider, 65 | }, 66 | Hash: MustCID(t, "zdpuAsJDrLKrAiU8M518eu6mgv9HzS3e1pfH5XC7LUsFgsK5c"), 67 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 1), 68 | }, 69 | 70 | { 71 | Payload: []byte("two"), 72 | LogID: "A", 73 | Next: []cid.Cid{MustCID(t, "zdpuAsJDrLKrAiU8M518eu6mgv9HzS3e1pfH5XC7LUsFgsK5c")}, 74 | V: 1, 75 | Key: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 76 | Sig: MustBytesFromHex(t, "3045022100b85c85c59e6d0952f95e3839e48b43b4073ef26f6f4696d785ce64053cd5869a0220644a4a7a15ddcd2b152611b08bf23b9df7823846719f2d0e4b0aff64190ed146"), 77 | Identity: &identityprovider.Identity{ 78 | ID: "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c", 79 | PublicKey: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 80 | Signatures: &identityprovider.IdentitySignature{ 81 | ID: MustBytesFromHex(t, "3045022100f5f6f10571d14347aaf34e526ce3419fd64d75ffa7aa73692cbb6aeb6fbc147102203a3e3fa41fa8fcbb9fc7c148af5b640e2f704b20b3a4e0b93fc3a6d44dffb41e"), 82 | PublicKey: MustBytesFromHex(t, "3044022020982b8492be0c184dc29de0a3a3bd86a86ba997756b0bf41ddabd24b47c5acf02203745fda39d7df650a5a478e52bbe879f0cb45c074025a93471414a56077640a4"), 83 | }, 84 | Type: "orbitdb", 85 | Provider: id.Provider, 86 | }, 87 | Hash: MustCID(t, "zdpuAxgKyiM9qkP9yPKCCqrHer9kCqYyr7KbhucsPwwfh6JB3"), 88 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 2), 89 | }, 90 | 91 | { 92 | Payload: []byte("three"), 93 | LogID: "A", 94 | Next: []cid.Cid{MustCID(t, "zdpuAxgKyiM9qkP9yPKCCqrHer9kCqYyr7KbhucsPwwfh6JB3")}, 95 | V: 1, 96 | Key: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 97 | Sig: MustBytesFromHex(t, "304402206f6a1582bc2c18b63eeb5b1e2280f2700c5d467d60185738702f90f4e655214602202ce0fb6de31b42a24768f274ecb4c1e2ed8529e073cfb361fc1ef5d1e2d75a31"), 98 | Identity: &identityprovider.Identity{ 99 | ID: "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c", 100 | PublicKey: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 101 | Signatures: &identityprovider.IdentitySignature{ 102 | ID: MustBytesFromHex(t, "3045022100f5f6f10571d14347aaf34e526ce3419fd64d75ffa7aa73692cbb6aeb6fbc147102203a3e3fa41fa8fcbb9fc7c148af5b640e2f704b20b3a4e0b93fc3a6d44dffb41e"), 103 | PublicKey: MustBytesFromHex(t, "3044022020982b8492be0c184dc29de0a3a3bd86a86ba997756b0bf41ddabd24b47c5acf02203745fda39d7df650a5a478e52bbe879f0cb45c074025a93471414a56077640a4"), 104 | }, 105 | Type: "orbitdb", 106 | Provider: id.Provider, 107 | }, 108 | Hash: MustCID(t, "zdpuAq7PAbQ7iavSdkNUUUrRUba5wSpRDJRsiC8RcvkXdgqYJ"), 109 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 3), 110 | }, 111 | 112 | { 113 | Payload: []byte("four"), 114 | LogID: "A", 115 | Next: []cid.Cid{MustCID(t, "zdpuAq7PAbQ7iavSdkNUUUrRUba5wSpRDJRsiC8RcvkXdgqYJ")}, 116 | V: 1, 117 | Key: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 118 | Sig: MustBytesFromHex(t, "30440220103ff89892856ec222d37b1244199cfb6e39629f155cd80ffa9b6e0b67de98940220391da8dc35e0b99f247c41676b8fb2337879d05dd343c55d9a89275c05076dcc"), 119 | Identity: &identityprovider.Identity{ 120 | ID: "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c", 121 | PublicKey: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 122 | Signatures: &identityprovider.IdentitySignature{ 123 | ID: MustBytesFromHex(t, "3045022100f5f6f10571d14347aaf34e526ce3419fd64d75ffa7aa73692cbb6aeb6fbc147102203a3e3fa41fa8fcbb9fc7c148af5b640e2f704b20b3a4e0b93fc3a6d44dffb41e"), 124 | PublicKey: MustBytesFromHex(t, "3044022020982b8492be0c184dc29de0a3a3bd86a86ba997756b0bf41ddabd24b47c5acf02203745fda39d7df650a5a478e52bbe879f0cb45c074025a93471414a56077640a4"), 125 | }, 126 | Type: "orbitdb", 127 | Provider: id.Provider, 128 | }, 129 | Hash: MustCID(t, "zdpuAqgCh78NCXffmFYv4DM2KfhhpY92agJ9sKRB2eq9B5mFA"), 130 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 4), 131 | }, 132 | 133 | { 134 | Payload: []byte("five"), 135 | LogID: "A", 136 | Next: []cid.Cid{MustCID(t, "zdpuAq7PAbQ7iavSdkNUUUrRUba5wSpRDJRsiC8RcvkXdgqYJ")}, 137 | V: 1, 138 | Key: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 139 | Sig: MustBytesFromHex(t, "3044022012a6bad4be1aabec23816bc8ccaf3cb41d43f06adb3f7d55b14fe2ddae37035a02204324d0b9481c351a1b6c391bd9cb960c039f102f950cf2a48fd8648f7615c51f"), 140 | Identity: &identityprovider.Identity{ 141 | ID: "03e0480538c2a39951d054e17ff31fde487cb1031d0044a037b53ad2e028a3e77c", 142 | PublicKey: MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 143 | Signatures: &identityprovider.IdentitySignature{ 144 | ID: MustBytesFromHex(t, "3045022100f5f6f10571d14347aaf34e526ce3419fd64d75ffa7aa73692cbb6aeb6fbc147102203a3e3fa41fa8fcbb9fc7c148af5b640e2f704b20b3a4e0b93fc3a6d44dffb41e"), 145 | PublicKey: MustBytesFromHex(t, "3044022020982b8492be0c184dc29de0a3a3bd86a86ba997756b0bf41ddabd24b47c5acf02203745fda39d7df650a5a478e52bbe879f0cb45c074025a93471414a56077640a4"), 146 | }, 147 | Type: "orbitdb", 148 | Provider: id.Provider, 149 | }, 150 | Hash: MustCID(t, "zdpuAwNuRc2Kc1aNDdcdSWuxfNpHRJQw8L8APBNHCEFuyU4Xf"), 151 | Clock: entry.NewLamportClock(MustBytesFromHex(t, "048bef2231e64d5c7147bd4b8afb84abd4126ee8d8335e4b069ac0a65c7be711cea5c1b8d47bc20ebaecdca588600ddf2894675e78b2ef17cf49e7bbaf98080361"), 4), 152 | }, 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /test/utils_logcreator_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | ipfslog "berty.tech/go-ipfs-log" 8 | idp "berty.tech/go-ipfs-log/identityprovider" 9 | coreiface "github.com/ipfs/kubo/core/coreiface" 10 | ) 11 | 12 | type CreatedLog struct { 13 | Log *ipfslog.IPFSLog 14 | ExpectedData []string 15 | JSON *ipfslog.JSONLog 16 | } 17 | 18 | func createLogsFor16Entries(ctx context.Context, ipfs coreiface.CoreAPI, identities []*idp.Identity) (*ipfslog.IPFSLog, error) { 19 | logA, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | logB, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "X"}) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | log3, err := ipfslog.NewLog(ipfs, identities[2], &ipfslog.LogOptions{ID: "X"}) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | l, err := ipfslog.NewLog(ipfs, identities[3], &ipfslog.LogOptions{ID: "X"}) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | for i := 1; i <= 5; i++ { 40 | _, err := logA.Append(ctx, []byte(fmt.Sprintf("entryA%d", i)), nil) 41 | if err != nil { 42 | return nil, err 43 | } 44 | } 45 | 46 | for i := 1; i <= 5; i++ { 47 | _, err := logB.Append(ctx, []byte(fmt.Sprintf("entryB%d", i)), nil) 48 | if err != nil { 49 | return nil, err 50 | } 51 | } 52 | 53 | _, err = log3.Join(logA, -1) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | _, err = log3.Join(logB, -1) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | for i := 6; i <= 10; i++ { 64 | _, err := logA.Append(ctx, []byte(fmt.Sprintf("entryA%d", i)), nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } 69 | 70 | _, err = l.Join(log3, -1) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | _, err = l.Append(ctx, []byte("entryC0"), nil) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | _, err = l.Join(logA, -1) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return l, nil 86 | } 87 | 88 | func CreateLogWithSixteenEntries(ctx context.Context, ipfs coreiface.CoreAPI, identities []*idp.Identity) (*CreatedLog, error) { 89 | expectedData := []string{ 90 | "entryA1", "entryB1", "entryA2", "entryB2", "entryA3", "entryB3", 91 | "entryA4", "entryB4", "entryA5", "entryB5", 92 | "entryA6", 93 | "entryC0", 94 | "entryA7", "entryA8", "entryA9", "entryA10", 95 | } 96 | 97 | l, err := createLogsFor16Entries(ctx, ipfs, identities) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | return &CreatedLog{Log: l, ExpectedData: expectedData, JSON: l.ToJSONLog()}, nil 103 | } 104 | 105 | func createLogWithHundredEntries(ctx context.Context, ipfs coreiface.CoreAPI, identities []*idp.Identity) (*ipfslog.IPFSLog, []string, error) { 106 | var expectedData []string 107 | const amount = 100 108 | 109 | logA, err := ipfslog.NewLog(ipfs, identities[0], &ipfslog.LogOptions{ID: "X"}) 110 | if err != nil { 111 | return nil, nil, err 112 | } 113 | 114 | logB, err := ipfslog.NewLog(ipfs, identities[1], &ipfslog.LogOptions{ID: "X"}) 115 | if err != nil { 116 | return nil, nil, err 117 | } 118 | 119 | for i := 1; i <= amount; i++ { 120 | entryNameA := fmt.Sprintf("entryA%d", i) 121 | entryNameB := fmt.Sprintf("entryB%d", i) 122 | 123 | _, err := logA.Append(ctx, []byte(entryNameA), nil) 124 | if err != nil { 125 | return nil, nil, err 126 | } 127 | _, err = logB.Join(logA, -1) 128 | if err != nil { 129 | return nil, nil, err 130 | } 131 | 132 | _, err = logB.Append(ctx, []byte(entryNameB), nil) 133 | if err != nil { 134 | return nil, nil, err 135 | } 136 | 137 | _, err = logA.Join(logB, -1) 138 | if err != nil { 139 | return nil, nil, err 140 | } 141 | 142 | expectedData = append(expectedData, entryNameA, entryNameB) 143 | } 144 | 145 | return logA, expectedData, nil 146 | } 147 | 148 | func CreateLogWithHundredEntries(ctx context.Context, ipfs coreiface.CoreAPI, identities []*idp.Identity) (*CreatedLog, error) { 149 | l, expectedData, err := createLogWithHundredEntries(ctx, ipfs, identities) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | return &CreatedLog{Log: l, ExpectedData: expectedData}, nil 155 | } 156 | -------------------------------------------------------------------------------- /test/utils_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "testing" 7 | 8 | "berty.tech/go-ipfs-log/iface" 9 | "github.com/ipfs/go-datastore" 10 | dssync "github.com/ipfs/go-datastore/sync" 11 | config "github.com/ipfs/kubo/config" 12 | ipfsCore "github.com/ipfs/kubo/core" 13 | "github.com/ipfs/kubo/core/coreapi" 14 | coreiface "github.com/ipfs/kubo/core/coreiface" 15 | mock "github.com/ipfs/kubo/core/mock" 16 | ipfs_repo "github.com/ipfs/kubo/repo" 17 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | func newRepo() (ipfs_repo.Repo, error) { 22 | // Generating config 23 | cfg, err := config.Init(io.Discard, 2048) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | // Listen on local interface only 29 | cfg.Addresses.Swarm = []string{ 30 | "/ip4/127.0.0.1/tcp/0", 31 | } 32 | // we don't need ressources manager for test 33 | cfg.Swarm.ResourceMgr.Enabled = config.False 34 | 35 | // Do not bootstrap on ipfs node 36 | cfg.Bootstrap = []string{} 37 | 38 | return &ipfs_repo.Mock{ 39 | D: dssync.MutexWrap(datastore.NewMapDatastore()), 40 | C: *cfg, 41 | }, nil 42 | } 43 | 44 | func NewMemoryServices(ctx context.Context, t testing.TB, m mocknet.Mocknet) (coreiface.CoreAPI, func()) { 45 | t.Helper() 46 | 47 | r, err := newRepo() 48 | require.NoError(t, err) 49 | 50 | core, err := ipfsCore.NewNode(ctx, &ipfsCore.BuildCfg{ 51 | Online: true, 52 | Repo: r, 53 | Host: mock.MockHostOption(m), 54 | ExtraOpts: map[string]bool{ 55 | "pubsub": true, 56 | }, 57 | }) 58 | require.NoError(t, err) 59 | 60 | api, err := coreapi.NewCoreAPI(core) 61 | require.NoError(t, err) 62 | 63 | close := func() { 64 | core.Close() 65 | } 66 | return api, close 67 | } 68 | 69 | func lastEntry(entries []iface.IPFSLogEntry) iface.IPFSLogEntry { 70 | length := len(entries) 71 | if length > 0 { 72 | return entries[len(entries)-1] 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func entriesAsStrings(values iface.IPFSLogOrderedEntries) []string { 79 | var foundEntries []string 80 | for _, v := range values.Slice() { 81 | foundEntries = append(foundEntries, string(v.GetPayload())) 82 | } 83 | 84 | return foundEntries 85 | } 86 | 87 | func getLastEntry(omap iface.IPFSLogOrderedEntries) iface.IPFSLogEntry { 88 | lastKey := omap.Keys()[len(omap.Keys())-1] 89 | 90 | return omap.UnsafeGet(lastKey) 91 | } 92 | 93 | func minInt(a, b int) int { 94 | if a < b { 95 | return a 96 | } 97 | return b 98 | } 99 | 100 | func intPtr(val int) *int { 101 | return &val 102 | } 103 | 104 | var bigLogString = `DONE 105 | └─EOF 106 | └─entryC10 107 | └─entryB10 108 | └─entryA10 109 | └─entryC9 110 | └─entryB9 111 | └─entryA9 112 | └─entryC8 113 | └─entryB8 114 | └─entryA8 115 | └─entryC7 116 | └─entryB7 117 | └─entryA7 118 | └─entryC6 119 | └─entryB6 120 | └─entryA6 121 | └─entryC5 122 | └─entryB5 123 | └─entryA5 124 | └─entryC4 125 | └─entryB4 126 | └─entryA4 127 | └─3 128 | └─entryC3 129 | └─entryB3 130 | └─entryA3 131 | └─2 132 | └─entryC2 133 | └─entryB2 134 | └─entryA2 135 | └─1 136 | └─entryC1 137 | └─entryB1 138 | └─entryA1` 139 | --------------------------------------------------------------------------------