+38
-4
abciapp/execution.go
+38
-4
abciapp/execution.go
···
8
8
9
9
abcitypes "github.com/cometbft/cometbft/abci/types"
10
10
"github.com/palantir/stacktrace"
11
+
"github.com/samber/lo"
11
12
)
12
13
13
14
// InitChain implements [types.Application].
···
19
20
// PrepareProposal implements [types.Application].
20
21
func (d *DIDPLCApplication) PrepareProposal(ctx context.Context, req *abcitypes.RequestPrepareProposal) (*abcitypes.ResponsePrepareProposal, error) {
21
22
defer d.tree.Rollback()
23
+
24
+
deps := TransactionProcessorDependencies{
25
+
plc: d.plc,
26
+
tree: d,
27
+
}
22
28
23
29
st := time.Now()
24
30
acceptedTx := make([][]byte, 0, len(req.Txs))
···
26
32
for {
27
33
toTryNext := [][]byte{}
28
34
for _, tx := range toProcess {
29
-
result, err := processTx(ctx, d.plc, tx, req.Time, true)
35
+
result, err := processTx(ctx, deps, tx, req.Time, true)
30
36
if err != nil {
31
37
return nil, stacktrace.Propagate(err, "")
32
38
}
33
39
40
+
if result.IsAuthoritativeImportTransaction {
41
+
// this type of transaction is not meant to appear in the mempool,
42
+
// but maybe it's not impossible that a non-compliant node could have gossiped it to us?
43
+
// (not sure if CometBFT checks transactions coming from other peers against CheckTx)
44
+
continue
45
+
}
46
+
34
47
if result.Code == 0 {
35
48
acceptedTx = append(acceptedTx, tx)
36
49
} else {
···
39
52
toTryNext = append(toTryNext, tx)
40
53
}
41
54
}
42
-
if len(toProcess) == len(toTryNext) {
55
+
if len(toProcess) >= len(toTryNext) {
43
56
// we made no progress in this iteration - all transactions left to process fail to do so
44
57
// so they can't be depending on anything that would be included in this block, at this point
45
58
// just continue while dropping the transactions that would never succeed in this block
···
52
65
toProcess = toTryNext
53
66
}
54
67
68
+
totalSize := lo.SumBy(acceptedTx, func(tx []byte) int { return len(tx) })
69
+
if totalSize < int(req.MaxTxBytes)-4096 {
70
+
// we have space to fit an import transaction
71
+
// TODO
72
+
}
73
+
55
74
return &abcitypes.ResponsePrepareProposal{Txs: acceptedTx}, nil
56
75
}
57
76
···
74
93
}
75
94
}()
76
95
96
+
deps := TransactionProcessorDependencies{
97
+
plc: d.plc,
98
+
tree: d,
99
+
}
100
+
77
101
txResults := make([]*abcitypes.ExecTxResult, len(req.Txs))
78
102
for i, tx := range req.Txs {
79
-
result, err := processTx(ctx, d.plc, tx, req.Time, true)
103
+
result, err := processTx(ctx, deps, tx, req.Time, true)
80
104
if err != nil {
81
105
return nil, stacktrace.Propagate(err, "")
82
106
}
···
86
110
return &abcitypes.ResponseProcessProposal{Status: abcitypes.ResponseProcessProposal_REJECT}, nil
87
111
}
88
112
113
+
if result.IsAuthoritativeImportTransaction && i != len(req.Txs)-1 {
114
+
// if an Authoritative Import transaction is present on the block, it must be the last one
115
+
return &abcitypes.ResponseProcessProposal{Status: abcitypes.ResponseProcessProposal_REJECT}, nil
116
+
}
117
+
89
118
txResults[i] = &abcitypes.ExecTxResult{
90
119
Code: result.Code,
91
120
Data: result.Data,
···
130
159
// discard the current modified state, and process the decided block
131
160
d.tree.Rollback()
132
161
162
+
deps := TransactionProcessorDependencies{
163
+
plc: d.plc,
164
+
tree: d,
165
+
}
166
+
133
167
txResults := make([]*abcitypes.ExecTxResult, len(req.Txs))
134
168
for i, tx := range req.Txs {
135
-
result, err := processTx(ctx, d.plc, tx, req.Time, true)
169
+
result, err := processTx(ctx, deps, tx, req.Time, true)
136
170
if err != nil {
137
171
return nil, stacktrace.Propagate(err, "")
138
172
}
+136
abciapp/import.go
+136
abciapp/import.go
···
1
+
package abciapp
2
+
3
+
import (
4
+
"bufio"
5
+
"context"
6
+
"crypto/sha256"
7
+
"encoding/binary"
8
+
"encoding/json"
9
+
"fmt"
10
+
"net/http"
11
+
"net/url"
12
+
"time"
13
+
14
+
"github.com/bluesky-social/indigo/atproto/syntax"
15
+
"github.com/did-method-plc/go-didplc"
16
+
"github.com/ipfs/go-cid"
17
+
"github.com/palantir/stacktrace"
18
+
"github.com/samber/lo"
19
+
"tangled.org/gbl08ma/didplcbft/store"
20
+
)
21
+
22
+
func fetchExportedBatchFromAuthoritativeSource(ctx context.Context, plcURL string, startAt, maxCount uint64) ([]didplc.LogEntry, uint64, error) {
23
+
baseURL, err := url.JoinPath(plcURL, "/export")
24
+
if err != nil {
25
+
return nil, 0, stacktrace.Propagate(err, "")
26
+
}
27
+
28
+
client := &http.Client{Timeout: 30 * time.Second}
29
+
30
+
entries := make([]didplc.LogEntry, 0, maxCount)
31
+
for {
32
+
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
33
+
if err != nil {
34
+
return nil, 0, stacktrace.Propagate(err, "")
35
+
}
36
+
37
+
req.Header.Set("User-Agent", "go-did-method-plc")
38
+
39
+
requestCount := min(1000, maxCount-uint64(len(entries)))
40
+
41
+
q := req.URL.Query()
42
+
q.Add("count", fmt.Sprint(requestCount))
43
+
q.Add("after", fmt.Sprint(startAt))
44
+
req.URL.RawQuery = q.Encode()
45
+
46
+
resp, err := client.Do(req)
47
+
if err != nil {
48
+
return nil, 0, stacktrace.Propagate(err, "")
49
+
}
50
+
defer resp.Body.Close()
51
+
52
+
if resp.StatusCode != http.StatusOK {
53
+
return nil, 0, stacktrace.NewError("non-200 status code")
54
+
}
55
+
56
+
type logEntryWithSeq struct {
57
+
didplc.LogEntry
58
+
Seq uint64 `json:"seq"`
59
+
}
60
+
61
+
// Read response body
62
+
s := bufio.NewScanner(resp.Body)
63
+
numEntriesThisResponse := 0
64
+
for s.Scan() && len(entries) < int(maxCount) {
65
+
var entry logEntryWithSeq
66
+
if err := json.Unmarshal(s.Bytes(), &entry); err != nil {
67
+
return nil, 0, stacktrace.Propagate(err, "")
68
+
}
69
+
entries = append(entries, entry.LogEntry)
70
+
numEntriesThisResponse++
71
+
startAt = entry.Seq
72
+
}
73
+
if s.Err() != nil {
74
+
return nil, 0, stacktrace.Propagate(s.Err(), "")
75
+
}
76
+
77
+
if uint64(numEntriesThisResponse) < requestCount || len(entries) >= int(maxCount) {
78
+
break
79
+
}
80
+
}
81
+
82
+
return entries, startAt, nil
83
+
}
84
+
85
+
func computeLogEntriesHash(logEntries []didplc.LogEntry) ([]byte, error) {
86
+
// let's _not_ rely on the specifics of the JSON representation
87
+
// (instead let's rely on specifics of our implementation, heh)
88
+
89
+
hash := sha256.New()
90
+
for i, entry := range logEntries {
91
+
// Write DID
92
+
didBytes, err := store.DIDToBytes(entry.DID)
93
+
if err != nil {
94
+
return nil, stacktrace.Propagate(err, "invalid DID in entry index %d", i)
95
+
}
96
+
97
+
_, err = hash.Write(didBytes)
98
+
if err != nil {
99
+
return nil, stacktrace.Propagate(err, "")
100
+
}
101
+
102
+
// Write CID
103
+
// (We trust that the authoritative source computed CIDs properly, so we use theirs rather than decoding the operation and recomputing)
104
+
cid, err := cid.Decode(entry.CID)
105
+
if err != nil {
106
+
return nil, stacktrace.Propagate(err, "invalid CID in entry index %d", i)
107
+
}
108
+
109
+
_, err = hash.Write(cid.Bytes())
110
+
if err != nil {
111
+
return nil, stacktrace.Propagate(err, "")
112
+
}
113
+
114
+
// Write CreatedAt
115
+
createdAt, err := syntax.ParseDatetime(entry.CreatedAt)
116
+
if err != nil {
117
+
return nil, stacktrace.Propagate(err, "invalid CreatedAt in entry index %d", i)
118
+
}
119
+
120
+
tsBytes := make([]byte, 8)
121
+
binary.BigEndian.PutUint64(tsBytes, uint64(createdAt.Time().Truncate(1*time.Millisecond).UTC().UnixNano()))
122
+
123
+
_, err = hash.Write(tsBytes)
124
+
if err != nil {
125
+
return nil, stacktrace.Propagate(err, "")
126
+
}
127
+
128
+
// Write Nullified
129
+
_, err = hash.Write([]byte{lo.Ternary[byte](entry.Nullified, 1, 0)})
130
+
if err != nil {
131
+
return nil, stacktrace.Propagate(err, "")
132
+
}
133
+
}
134
+
135
+
return hash.Sum(nil), nil
136
+
}
+13
-1
abciapp/mempool.go
+13
-1
abciapp/mempool.go
···
10
10
11
11
// CheckTx implements [types.Application].
12
12
func (d *DIDPLCApplication) CheckTx(ctx context.Context, req *abcitypes.RequestCheckTx) (*abcitypes.ResponseCheckTx, error) {
13
-
result, err := processTx(ctx, d.plc, req.Tx, time.Now(), false)
13
+
deps := TransactionProcessorDependencies{
14
+
plc: d.plc,
15
+
tree: d,
16
+
}
17
+
18
+
result, err := processTx(ctx, deps, req.Tx, time.Now(), false)
14
19
if err != nil {
15
20
return nil, stacktrace.Propagate(err, "")
21
+
}
22
+
if result.IsAuthoritativeImportTransaction {
23
+
// this type of transaction is meant to be included only by validator nodes
24
+
return &abcitypes.ResponseCheckTx{
25
+
Code: 4002,
26
+
Info: "AuthoritativeImport transactions can only be introduced by validator nodes",
27
+
}, nil
16
28
}
17
29
return &abcitypes.ResponseCheckTx{
18
30
Code: result.Code,
+19
-18
abciapp/tx.go
+19
-18
abciapp/tx.go
···
17
17
18
18
type TransactionAction string
19
19
20
-
var (
21
-
knownActions = map[TransactionAction]struct{}{}
22
-
TransactionActionCreatePlcOp = registerTransactionAction[CreatePlcOpArguments]("CreatePlcOp")
23
-
TransactionActionAuthoritativeImport = registerTransactionAction[AuthoritativeImportArguments]("AuthoritativeImport")
24
-
)
20
+
type TransactionProcessorDependencies struct {
21
+
plc plc.PLC
22
+
tree plc.TreeProvider // TODO maybe we should move the TreeProvider definition out of the plc package then?
23
+
}
24
+
25
+
type TransactionProcessor func(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte, atTime time.Time, execute bool) (*processResult, error)
26
+
27
+
var knownActions = map[TransactionAction]TransactionProcessor{}
25
28
26
-
func registerTransactionAction[ArgType ArgumentType](action string) TransactionAction {
29
+
func registerTransactionAction[ArgType ArgumentType](action string, processor TransactionProcessor) TransactionAction {
27
30
ta := TransactionAction(action)
28
31
if _, present := knownActions[ta]; present {
29
32
panic("action already registered")
···
32
35
if argType.ForAction() != "" && argType.ForAction() != ta {
33
36
panic("mismatched argument types")
34
37
}
35
-
knownActions[ta] = struct{}{}
38
+
knownActions[ta] = processor
36
39
return ta
37
40
}
38
41
···
79
82
}
80
83
81
84
type processResult struct {
85
+
IsAuthoritativeImportTransaction bool
86
+
82
87
Code uint32
83
88
Data []byte
84
89
Log string
···
89
94
Codespace string
90
95
}
91
96
92
-
func processTx(ctx context.Context, p plc.PLC, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
97
+
func processTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
93
98
if !IsTransactionSanitized(txBytes) {
94
99
return &processResult{
95
100
Code: 4000,
···
119
124
}, nil
120
125
}
121
126
122
-
var result *processResult
123
-
switch TransactionAction(action) {
124
-
case TransactionActionCreatePlcOp:
125
-
result, err = processCreatePlcOpTx(ctx, p, txBytes, atTime, execute)
126
-
case TransactionActionAuthoritativeImport:
127
-
result, err = processAuthoritativeImportTx(ctx, p, txBytes, atTime, execute)
128
-
default:
129
-
result = &processResult{
127
+
processor, ok := knownActions[TransactionAction(action)]
128
+
if !ok {
129
+
return &processResult{
130
130
Code: 4001,
131
131
Info: "Unknown transaction action",
132
-
}
133
-
132
+
}, nil
134
133
}
134
+
135
+
result, err := processor(ctx, deps, txBytes, atTime, execute)
135
136
return result, stacktrace.Propagate(err, "")
136
137
}
+5
-3
abciapp/tx_create_plc_op.go
+5
-3
abciapp/tx_create_plc_op.go
···
11
11
"tangled.org/gbl08ma/didplcbft/plc"
12
12
)
13
13
14
+
var TransactionActionCreatePlcOp = registerTransactionAction[CreatePlcOpArguments]("CreatePlcOp", processCreatePlcOpTx)
15
+
14
16
type CreatePlcOpArguments struct {
15
17
DID string `json:"did" refmt:"did"`
16
18
Operation *didplc.OpEnum `refmt:"operation"`
···
25
27
cbornode.RegisterCborType(Transaction[CreatePlcOpArguments]{})
26
28
}
27
29
28
-
func processCreatePlcOpTx(ctx context.Context, p plc.PLC, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
30
+
func processCreatePlcOpTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
29
31
tx, err := UnmarshalTransaction[CreatePlcOpArguments](txBytes)
30
32
if err != nil {
31
33
return &processResult{
···
52
54
}
53
55
54
56
if execute {
55
-
err = p.ExecuteOperation(ctx, atTime, tx.Arguments.DID, opBytes)
57
+
err = deps.plc.ExecuteOperation(ctx, atTime, tx.Arguments.DID, opBytes)
56
58
} else {
57
-
err = p.ValidateOperation(ctx, plc.CommittedTreeVersion, atTime, tx.Arguments.DID, opBytes)
59
+
err = deps.plc.ValidateOperation(ctx, plc.CommittedTreeVersion, atTime, tx.Arguments.DID, opBytes)
58
60
}
59
61
if err != nil {
60
62
if code, ok := plc.InvalidOperationErrorCode(err); ok {
+170
-4
abciapp/tx_import.go
+170
-4
abciapp/tx_import.go
···
2
2
3
3
import (
4
4
"context"
5
+
"encoding/hex"
6
+
"net/url"
5
7
"time"
6
8
9
+
"github.com/did-method-plc/go-didplc"
7
10
cbornode "github.com/ipfs/go-ipld-cbor"
8
11
"github.com/palantir/stacktrace"
9
12
"tangled.org/gbl08ma/didplcbft/plc"
13
+
"tangled.org/gbl08ma/didplcbft/store"
10
14
)
11
15
16
+
var TransactionActionSetAuthoritativePlc = registerTransactionAction[SetAuthoritativePlcArguments]("SetAuthoritativePlc", processSetAuthoritativePlcTx)
17
+
18
+
type SetAuthoritativePlcArguments struct {
19
+
PLCURL string `json:"plcURL" refmt:"plcURL"`
20
+
RestartImport bool `json:"restartImport" refmt:"restartImport"`
21
+
}
22
+
23
+
func (SetAuthoritativePlcArguments) ForAction() TransactionAction {
24
+
return TransactionActionSetAuthoritativePlc
25
+
}
26
+
27
+
func init() {
28
+
cbornode.RegisterCborType(SetAuthoritativePlcArguments{})
29
+
cbornode.RegisterCborType(Transaction[SetAuthoritativePlcArguments]{})
30
+
}
31
+
32
+
func processSetAuthoritativePlcTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
33
+
tx, err := UnmarshalTransaction[SetAuthoritativePlcArguments](txBytes)
34
+
if err != nil {
35
+
return &processResult{
36
+
Code: 4000,
37
+
Info: err.Error(),
38
+
}, nil
39
+
}
40
+
41
+
// TODO this transaction must somehow validate that whoever submitted it has the permission to change this
42
+
// Does it even make sense to keep this operation type as something submitted via the mempool in the long run,
43
+
// or would it be tied to some sort of proposal/participation system, where the validators submit this operation type in response to some on-chain trigger?
44
+
45
+
// A simple solution in the short term might be to just validate a "simple" public-private signature + an expiry timestamp (to prevent replay attacks)
46
+
// which would both be part of the SetAuthoritativePlcArguments. Very centralized, but very straightforward
47
+
// (the public key would be part of the config or even hardcoded for good measure)
48
+
49
+
if tx.Arguments.PLCURL != "" {
50
+
parsed, err := url.Parse(tx.Arguments.PLCURL)
51
+
if err != nil || parsed.Scheme != "https" {
52
+
return &processResult{
53
+
Code: 4100,
54
+
Info: "Malformed Authoritative PLC URL",
55
+
}, nil
56
+
}
57
+
}
58
+
59
+
if execute {
60
+
tree, err := deps.tree.MutableTree()
61
+
if err != nil {
62
+
return nil, stacktrace.Propagate(err, "")
63
+
}
64
+
err = store.Tree.SetAuthoritativePLC(tree, tx.Arguments.PLCURL)
65
+
if err != nil {
66
+
return nil, stacktrace.Propagate(err, "")
67
+
}
68
+
69
+
if tx.Arguments.RestartImport {
70
+
err = store.Tree.SetAuthoritativeImportProgress(tree, 0)
71
+
if err != nil {
72
+
return nil, stacktrace.Propagate(err, "")
73
+
}
74
+
}
75
+
}
76
+
77
+
return &processResult{
78
+
Code: 0,
79
+
}, nil
80
+
}
81
+
82
+
var TransactionActionAuthoritativeImport = registerTransactionAction[AuthoritativeImportArguments]("AuthoritativeImport", processAuthoritativeImportTx)
83
+
12
84
type AuthoritativeImportArguments struct {
13
-
Cursor string `json:"cursor" refmt:"cursor"`
85
+
PLCURL string `json:"plcURL" refmt:"plcURL"`
86
+
Cursor uint64 `json:"cursor" refmt:"cursor"`
87
+
Count uint64 `json:"count" refmt:"count"`
14
88
Hash string `json:"hash" refmt:"hash"`
15
89
}
16
90
···
23
97
cbornode.RegisterCborType(Transaction[AuthoritativeImportArguments]{})
24
98
}
25
99
26
-
func processAuthoritativeImportTx(ctx context.Context, p plc.PLC, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
27
-
// TODO
28
-
return nil, stacktrace.NewError("not implemented")
100
+
func processAuthoritativeImportTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte, atTime time.Time, execute bool) (*processResult, error) {
101
+
tx, err := UnmarshalTransaction[AuthoritativeImportArguments](txBytes)
102
+
if err != nil {
103
+
return &processResult{
104
+
Code: 4000,
105
+
Info: err.Error(),
106
+
}, nil
107
+
}
108
+
109
+
roTree, err := deps.tree.ImmutableTree(plc.CommittedTreeVersion)
110
+
if err != nil {
111
+
return nil, stacktrace.Propagate(err, "")
112
+
}
113
+
114
+
expectedPlcUrl, err := store.Tree.AuthoritativePLC(roTree)
115
+
if err != nil {
116
+
return nil, stacktrace.Propagate(err, "")
117
+
}
118
+
119
+
if expectedPlcUrl != tx.Arguments.PLCURL {
120
+
return &processResult{
121
+
Code: 4110,
122
+
Info: "Unexpected Authoritative PLC URL",
123
+
}, nil
124
+
}
125
+
126
+
expectedCursor, err := store.Tree.AuthoritativeImportProgress(roTree)
127
+
if err != nil {
128
+
return nil, stacktrace.Propagate(err, "")
129
+
}
130
+
131
+
if expectedCursor != tx.Arguments.Cursor {
132
+
return &processResult{
133
+
Code: 4111,
134
+
Info: "Unexpected import cursor",
135
+
}, nil
136
+
}
137
+
138
+
// TODO this shouldn't be happening synchronously! We should always be ahead of the next transaction!
139
+
// or at the very least it should only happen once (e.g. when processing the proposal) and then we should cache until it expires or until we actually commit
140
+
operations, newCursor, err := fetchExportedBatchFromAuthoritativeSource(ctx, expectedPlcUrl, expectedCursor, tx.Arguments.Count)
141
+
if err != nil {
142
+
// returning an actual error like this means "consensus failure". Probably not the best way to deal with this, we would rather drop the transaction if not all nodes can fetch the same thing
143
+
// TODO investigate
144
+
return nil, stacktrace.Propagate(err, "")
145
+
}
146
+
147
+
expectedHashBytes, err := computeLogEntriesHash(operations)
148
+
if err != nil {
149
+
return nil, stacktrace.Propagate(err, "")
150
+
}
151
+
152
+
if hex.EncodeToString(expectedHashBytes) != tx.Arguments.Hash {
153
+
return &processResult{
154
+
Code: 4112,
155
+
Info: "Unexpected import hash",
156
+
}, nil
157
+
}
158
+
159
+
if execute {
160
+
tree, err := deps.tree.MutableTree()
161
+
if err != nil {
162
+
return nil, stacktrace.Propagate(err, "")
163
+
}
164
+
165
+
var client didplc.Client
166
+
for _, entry := range operations {
167
+
err := deps.plc.ImportOperationFromAuthoritativeSource(ctx, entry, func() ([]didplc.LogEntry, error) {
168
+
// TODO Oh NOOOOOOO! This is not deterministic
169
+
// (fetched at different times, the audit log might grow, therefore we'll fetch and insert more ops, and change the apphash)
170
+
// we need to either limit how much audit log we return (only doable if how much was fetched for each op was part of the tx, ugh)
171
+
// or (probably preferred approach) make it so that the ImportOperationFromAuthoritativeSource / ReplaceHistory function only replaces up until the CID that's being imported, and no further ops
172
+
// Even then there is a problem: what if the nullified status changes between imports :dizzy_face:
173
+
// (can the nullified status change for the ops that are being imported only up until CID? Need to think)
174
+
e, err := client.AuditLog(ctx, entry.DID)
175
+
return e, stacktrace.Propagate(err, "")
176
+
})
177
+
if err != nil {
178
+
return nil, stacktrace.Propagate(err, "")
179
+
}
180
+
}
181
+
err = store.Tree.SetAuthoritativeImportProgress(tree, newCursor)
182
+
if err != nil {
183
+
return nil, stacktrace.Propagate(err, "")
184
+
}
185
+
}
186
+
187
+
// TODO finish implementation
188
+
// 1. if execute is true: actually import the operations
189
+
// 2. if execute is true: update AuthoritativeImportProgress
190
+
191
+
return &processResult{
192
+
IsAuthoritativeImportTransaction: true,
193
+
Code: 0,
194
+
}, stacktrace.NewError("not implemented")
29
195
}
+46
-5
store/tree.go
+46
-5
store/tree.go
···
29
29
ExportOperations(tree ReadOnlyTree, after uint64, count int) ([]types.SequencedLogEntry, error) // passing a count of zero means unlimited
30
30
StoreOperation(tree *iavl.MutableTree, entry didplc.LogEntry, nullifyWithIndexEqualOrGreaterThan mo.Option[int]) error
31
31
ReplaceHistory(tree *iavl.MutableTree, history []didplc.LogEntry) error
32
+
33
+
AuthoritativePLC(tree ReadOnlyTree) (string, error)
34
+
SetAuthoritativePLC(tree *iavl.MutableTree, url string) error
35
+
36
+
AuthoritativeImportProgress(tree ReadOnlyTree) (uint64, error)
37
+
SetAuthoritativeImportProgress(tree *iavl.MutableTree, nextCursor uint64) error
32
38
}
33
39
34
40
var _ PLCTreeStore = (*TreeStore)(nil)
···
39
45
func (t *TreeStore) AuditLog(tree ReadOnlyTree, did string, withProof bool) ([]types.SequencedLogEntry, *ics23.CommitmentProof, error) {
40
46
proofs := []*ics23.CommitmentProof{}
41
47
42
-
didBytes, err := didToBytes(did)
48
+
didBytes, err := DIDToBytes(did)
43
49
if err != nil {
44
50
return nil, nil, stacktrace.Propagate(err, "")
45
51
}
···
106
112
107
113
func (t *TreeStore) AuditLogReverseIterator(tree ReadOnlyTree, did string, retErr *error) iter.Seq2[int, types.SequencedLogEntry] {
108
114
return func(yield func(int, types.SequencedLogEntry) bool) {
109
-
didBytes, err := didToBytes(did)
115
+
didBytes, err := DIDToBytes(did)
110
116
if err != nil {
111
117
*retErr = stacktrace.Propagate(err, "")
112
118
return
···
179
185
}
180
186
181
187
func (t *TreeStore) StoreOperation(tree *iavl.MutableTree, entry didplc.LogEntry, nullifyWithIndexEqualOrGreaterThan mo.Option[int]) error {
182
-
didBytes, err := didToBytes(entry.DID)
188
+
didBytes, err := DIDToBytes(entry.DID)
183
189
if err != nil {
184
190
return stacktrace.Propagate(err, "")
185
191
}
···
251
257
252
258
did := remoteHistory[0].DID
253
259
254
-
didBytes, err := didToBytes(did)
260
+
didBytes, err := DIDToBytes(did)
255
261
if err != nil {
256
262
return stacktrace.Propagate(err, "")
257
263
}
···
361
367
return seq + 1, stacktrace.Propagate(err, "")
362
368
}
363
369
364
-
func didToBytes(did string) ([]byte, error) {
370
+
func DIDToBytes(did string) ([]byte, error) {
365
371
if !strings.HasPrefix(did, "did:plc:") {
366
372
return nil, stacktrace.NewError("invalid did:plc")
367
373
}
···
523
529
})).
524
530
Complete())
525
531
}
532
+
533
+
func (t *TreeStore) AuthoritativePLC(tree ReadOnlyTree) (string, error) {
534
+
url, err := tree.Get([]byte("aPLCURL"))
535
+
if err != nil {
536
+
return "", stacktrace.Propagate(err, "")
537
+
}
538
+
if url == nil {
539
+
return "", nil
540
+
}
541
+
return string(url), nil
542
+
}
543
+
544
+
func (t *TreeStore) SetAuthoritativePLC(tree *iavl.MutableTree, url string) error {
545
+
_, err := tree.Set([]byte("aPLCURL"), []byte(url))
546
+
return stacktrace.Propagate(err, "")
547
+
}
548
+
549
+
func (t *TreeStore) AuthoritativeImportProgress(tree ReadOnlyTree) (uint64, error) {
550
+
progBytes, err := tree.Get([]byte("aImportProgress"))
551
+
if err != nil {
552
+
return 0, stacktrace.Propagate(err, "")
553
+
}
554
+
if len(progBytes) != 8 {
555
+
return 0, nil
556
+
}
557
+
return binary.BigEndian.Uint64(progBytes), nil
558
+
}
559
+
560
+
func (t *TreeStore) SetAuthoritativeImportProgress(tree *iavl.MutableTree, nextCursor uint64) error {
561
+
value := make([]byte, 8)
562
+
binary.BigEndian.PutUint64(value, nextCursor)
563
+
564
+
_, err := tree.Set([]byte("aImportProgress"), value)
565
+
return stacktrace.Propagate(err, "")
566
+
}