A very experimental PLC implementation which uses BFT consensus for decentralization

Implement entirety of PLC API in ABCI Query method

gbl08ma.com 59f183f7 3a3acdb4

verified
+138 -85
+98 -45
abciapp/info.go
··· 11 11 "time" 12 12 13 13 abcitypes "github.com/cometbft/cometbft/abci/types" 14 + "github.com/did-method-plc/go-didplc" 14 15 "github.com/gbl08ma/stacktrace" 15 16 "github.com/ucarion/urlpath" 16 17 "tangled.org/gbl08ma.com/didplcbft/plc" ··· 39 40 if err != nil || url.Host != "" || url.Scheme != "" { 40 41 return &abcitypes.ResponseQuery{ 41 42 Code: 6000, 42 - Info: "Invalid path", 43 + Log: "Invalid path", 43 44 }, nil 44 45 } 45 46 ··· 53 54 if err != nil { 54 55 return &abcitypes.ResponseQuery{ 55 56 Code: 6001, 56 - Info: "Unavailable height", 57 + Log: "Unavailable height", 57 58 }, nil 58 59 } 59 60 ··· 66 67 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 67 68 did := match.Params["did"] 68 69 doc, err := d.plc.Resolve(ctx, readTx, did) 69 - if err != nil { 70 - switch { 71 - case errors.Is(err, plc.ErrDIDNotFound): 72 - return &abcitypes.ResponseQuery{ 73 - Key: []byte(did), 74 - Code: http.StatusNotFound, 75 - Info: "DID not registered: " + did, 76 - }, nil 77 - case errors.Is(err, plc.ErrDIDGone): 78 - return &abcitypes.ResponseQuery{ 79 - Key: []byte(did), 80 - Code: http.StatusGone, 81 - Info: "DID not available: " + did, 82 - }, nil 83 - default: 84 - return nil, stacktrace.Propagate(err) 85 - } 70 + if resp := handlePLCError(err, did); resp != nil { 71 + return resp, nil 72 + } else if err != nil { 73 + return nil, stacktrace.Propagate(err) 86 74 } 87 75 88 76 docJSON, err := json.Marshal(doc) ··· 100 88 { 101 89 matcher: urlpath.New("/plc/:did/log"), 102 90 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 91 + did := match.Params["did"] 92 + ops, err := d.plc.OperationLog(ctx, readTx, did) 93 + if resp := handlePLCError(err, did); resp != nil { 94 + return resp, nil 95 + } else if err != nil { 96 + return nil, stacktrace.Propagate(err) 97 + } 98 + 99 + opsJSON, err := json.Marshal(ops) 100 + if err != nil { 101 + return nil, stacktrace.Propagate(err) 102 + } 103 + 103 104 return &abcitypes.ResponseQuery{ 104 - Code: 1, 105 - Info: "Not implemented", 105 + Key: []byte(did), 106 + Value: []byte(opsJSON), 107 + Code: 0, 106 108 }, nil 107 109 }, 108 110 }, 109 111 { 110 112 matcher: urlpath.New("/plc/:did/log/audit"), 111 113 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 114 + did := match.Params["did"] 115 + entries, err := d.plc.AuditLog(ctx, readTx, did) 116 + if resp := handlePLCError(err, did); resp != nil { 117 + return resp, nil 118 + } else if err != nil { 119 + return nil, stacktrace.Propagate(err) 120 + } 121 + 122 + entriesJSON, err := json.Marshal(entries) 123 + if err != nil { 124 + return nil, stacktrace.Propagate(err) 125 + } 126 + 112 127 return &abcitypes.ResponseQuery{ 113 - Code: 1, 114 - Info: "Not implemented", 128 + Key: []byte(did), 129 + Value: []byte(entriesJSON), 130 + Code: 0, 115 131 }, nil 116 132 }, 117 133 }, 118 134 { 119 135 matcher: urlpath.New("/plc/:did/log/last"), 120 136 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 137 + did := match.Params["did"] 138 + op, err := d.plc.LastOperation(ctx, readTx, did) 139 + if resp := handlePLCError(err, did); resp != nil { 140 + return resp, nil 141 + } else if err != nil { 142 + return nil, stacktrace.Propagate(err) 143 + } 144 + 145 + opJSON, err := json.Marshal(op) 146 + if err != nil { 147 + return nil, stacktrace.Propagate(err) 148 + } 149 + 121 150 return &abcitypes.ResponseQuery{ 122 - Code: 1, 123 - Info: "Not implemented", 151 + Key: []byte(did), 152 + Value: []byte(opJSON), 153 + Code: 0, 124 154 }, nil 125 155 }, 126 156 }, ··· 129 159 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 130 160 did := match.Params["did"] 131 161 data, err := d.plc.Data(ctx, readTx, did) 132 - if err != nil { 133 - switch { 134 - case errors.Is(err, plc.ErrDIDNotFound): 135 - return &abcitypes.ResponseQuery{ 136 - Key: []byte(did), 137 - Code: http.StatusNotFound, 138 - Info: "DID not registered: " + did, 139 - }, nil 140 - case errors.Is(err, plc.ErrDIDGone): 141 - return &abcitypes.ResponseQuery{ 142 - Key: []byte(did), 143 - Code: http.StatusGone, 144 - Info: "DID not available: " + did, 145 - }, nil 146 - default: 147 - return nil, stacktrace.Propagate(err) 148 - } 162 + if resp := handlePLCError(err, did); resp != nil { 163 + return resp, nil 164 + } else if err != nil { 165 + return nil, stacktrace.Propagate(err) 166 + } 167 + 168 + resp := struct { 169 + DID string `json:"did"` 170 + RotationKeys []string `json:"rotationKeys"` 171 + VerificationMethods map[string]string `json:"verificationMethods"` 172 + AlsoKnownAs []string `json:"alsoKnownAs"` 173 + Services map[string]didplc.OpService `json:"services"` 174 + }{ 175 + DID: did, 176 + RotationKeys: data.RotationKeys, 177 + VerificationMethods: data.VerificationMethods, 178 + AlsoKnownAs: data.AlsoKnownAs, 179 + Services: data.Services, 149 180 } 150 181 151 - dataJSON, err := json.Marshal(&data) 182 + respJSON, err := json.Marshal(&resp) 152 183 if err != nil { 153 184 return nil, stacktrace.Propagate(err) 154 185 } 155 186 156 187 return &abcitypes.ResponseQuery{ 157 188 Key: []byte(did), 158 - Value: []byte(dataJSON), 189 + Value: []byte(respJSON), 159 190 Code: 0, 160 191 }, nil 161 192 }, ··· 170 201 171 202 return &abcitypes.ResponseQuery{ 172 203 Code: 6000, 173 - Info: "Invalid path", 204 + Log: "Invalid path", 174 205 }, nil 175 206 176 207 } 208 + 209 + func handlePLCError(err error, did string) *abcitypes.ResponseQuery { 210 + if err == nil { 211 + return nil 212 + } 213 + switch { 214 + case errors.Is(err, plc.ErrDIDNotFound): 215 + return &abcitypes.ResponseQuery{ 216 + Key: []byte(did), 217 + Code: http.StatusNotFound, 218 + Log: "DID not registered: " + did, 219 + } 220 + case errors.Is(err, plc.ErrDIDGone): 221 + return &abcitypes.ResponseQuery{ 222 + Key: []byte(did), 223 + Code: http.StatusGone, 224 + Log: "DID not available: " + did, 225 + } 226 + default: 227 + return nil 228 + } 229 + }
+1 -1
abciapp/mempool.go
··· 18 18 // this type of transaction is meant to be included only by validator nodes 19 19 return &abcitypes.ResponseCheckTx{ 20 20 Code: 4002, 21 - Info: "AuthoritativeImport transactions can only be introduced by validator nodes", 21 + Log: "AuthoritativeImport transactions can only be introduced by validator nodes", 22 22 }, nil 23 23 } 24 24
+5 -5
abciapp/tx.go
··· 197 197 if !IsTransactionSanitized(txBytes) { 198 198 return &processResult{ 199 199 Code: 4000, 200 - Info: "Transaction bytes do not follow canonical serialization format", 200 + Log: "Transaction bytes do not follow canonical serialization format", 201 201 }, "", nil, nil 202 202 } 203 203 var v map[string]interface{} ··· 205 205 if err != nil { 206 206 return &processResult{ 207 207 Code: 4001, 208 - Info: "Invalid transaction", 208 + Log: "Invalid transaction", 209 209 }, "", nil, nil 210 210 } 211 211 actionInterface, ok := v["action"] 212 212 if !ok { 213 213 return &processResult{ 214 214 Code: 4001, 215 - Info: "Unknown transaction action", 215 + Log: "Unknown transaction action", 216 216 }, "", nil, nil 217 217 } 218 218 actionString, ok := actionInterface.(string) 219 219 if !ok { 220 220 return &processResult{ 221 221 Code: 4001, 222 - Info: "Unknown transaction action", 222 + Log: "Unknown transaction action", 223 223 }, "", nil, nil 224 224 } 225 225 ··· 229 229 if !ok { 230 230 return &processResult{ 231 231 Code: 4001, 232 - Info: "Unknown transaction action", 232 + Log: "Unknown transaction action", 233 233 }, "", nil, nil 234 234 } 235 235
+23 -23
abciapp/tx_challenge.go
··· 61 61 if err != nil { 62 62 return &processResult{ 63 63 Code: 4000, 64 - Info: err.Error(), 64 + Log: err.Error(), 65 65 }, nil 66 66 } 67 67 ··· 73 73 if !verified { 74 74 return &processResult{ 75 75 Code: 4200, 76 - Info: "invalid signature", 76 + Log: "invalid signature", 77 77 }, nil 78 78 } 79 79 ··· 82 82 if tx.Arguments.ToHeight < tx.Arguments.FromHeight || tx.Arguments.ToHeight >= deps.workingHeight { 83 83 return &processResult{ 84 84 Code: 4201, 85 - Info: "invalid challenge range", 85 + Log: "invalid challenge range", 86 86 }, nil 87 87 } 88 88 ··· 90 90 if rangeSize < CommitToChallengeMinRange { 91 91 return &processResult{ 92 92 Code: 4202, 93 - Info: "insufficient challenge range", 93 + Log: "insufficient challenge range", 94 94 }, nil 95 95 } 96 96 if rangeSize > CommitToChallengeMaxRange { 97 97 return &processResult{ 98 98 Code: 4203, 99 - Info: "excessive challenge range", 99 + Log: "excessive challenge range", 100 100 }, nil 101 101 } 102 102 103 103 if tx.Arguments.ToHeight+CommitToChallengeMaxAgeInBlocks < deps.workingHeight { 104 104 return &processResult{ 105 105 Code: 4204, 106 - Info: "outdated challenge range", 106 + Log: "outdated challenge range", 107 107 }, nil 108 108 } 109 109 ··· 111 111 if toHeightBlockMeta == nil { 112 112 return &processResult{ 113 113 Code: 4205, 114 - Info: "unknown block in challenge range", 114 + Log: "unknown block in challenge range", 115 115 }, nil 116 116 } 117 117 118 118 if deps.readTx.Timestamp().Sub(toHeightBlockMeta.Header.Time) > CommitToChallengeMaxAge { 119 119 return &processResult{ 120 120 Code: 4206, 121 - Info: "outdated challenge range", 121 + Log: "outdated challenge range", 122 122 }, nil 123 123 } 124 124 ··· 127 127 if tx.Arguments.FromHeight <= int64(currentCompletion) { 128 128 return &processResult{ 129 129 Code: 4207, 130 - Info: "challenge range overlaps already completed challenge", 130 + Log: "challenge range overlaps already completed challenge", 131 131 }, nil 132 132 } 133 133 } else if !errors.Is(err, store.ErrNoRecentChallengeCompletion) { ··· 137 137 if len(tx.Arguments.Root) != 32 { 138 138 return &processResult{ 139 139 Code: 4208, 140 - Info: "invalid root", 140 + Log: "invalid root", 141 141 }, nil 142 142 } 143 143 ··· 146 146 if err != nil || proof.GetExist() == nil { 147 147 return &processResult{ 148 148 Code: 4209, 149 - Info: "invalid proof", 149 + Log: "invalid proof", 150 150 }, nil 151 151 } 152 152 existenceProof := proof.GetExist() ··· 163 163 if proofHeight != expectedProofHeight { 164 164 return &processResult{ 165 165 Code: 4210, 166 - Info: "invalid proof", 166 + Log: "invalid proof", 167 167 }, nil 168 168 } 169 169 ··· 174 174 if !blockProofValid { 175 175 return &processResult{ 176 176 Code: 4211, 177 - Info: "invalid proof", 177 + Log: "invalid proof", 178 178 }, nil 179 179 } 180 180 181 181 if !ics23.VerifyMembership(ics23.IavlSpec, tx.Arguments.Root, proof, existenceProof.Key, existenceProof.Value) { 182 182 return &processResult{ 183 183 Code: 4212, 184 - Info: "invalid proof", 184 + Log: "invalid proof", 185 185 }, nil 186 186 } 187 187 ··· 232 232 if err != nil { 233 233 return &processResult{ 234 234 Code: 4000, 235 - Info: err.Error(), 235 + Log: err.Error(), 236 236 }, nil 237 237 } 238 238 ··· 241 241 if errors.Is(err, store.ErrNoActiveChallengeCommitment) { 242 242 return &processResult{ 243 243 Code: 4300, 244 - Info: "validator is not committed to a challenge", 244 + Log: "validator is not committed to a challenge", 245 245 }, nil 246 246 } 247 247 return nil, stacktrace.Propagate(err) ··· 251 251 // validator must commit to a new challenge 252 252 return &processResult{ 253 253 Code: 4301, 254 - Info: "outdated challenge commitment", 254 + Log: "outdated challenge commitment", 255 255 }, nil 256 256 } 257 257 ··· 260 260 // this shouldn't happen unless the prover is submitting the completion on the same block as the commitment 261 261 return &processResult{ 262 262 Code: 4302, 263 - Info: "premature challenge completion", 263 + Log: "premature challenge completion", 264 264 }, nil 265 265 } 266 266 ··· 268 268 // validator must commit to a new challenge 269 269 return &processResult{ 270 270 Code: 4303, 271 - Info: "outdated challenge commitment", 271 + Log: "outdated challenge commitment", 272 272 }, nil 273 273 } 274 274 ··· 277 277 if err != nil || proof.GetExist() == nil { 278 278 return &processResult{ 279 279 Code: 4304, 280 - Info: "invalid proof", 280 + Log: "invalid proof", 281 281 }, nil 282 282 } 283 283 existenceProof := proof.GetExist() ··· 294 294 if proofHeight != expectedProofHeight { 295 295 return &processResult{ 296 296 Code: 4305, 297 - Info: "incorrect key proven", 297 + Log: "incorrect key proven", 298 298 }, nil 299 299 } 300 300 ··· 305 305 if !blockProofValid { 306 306 return &processResult{ 307 307 Code: 4306, 308 - Info: "invalid proof", 308 + Log: "invalid proof", 309 309 }, nil 310 310 } 311 311 312 312 if !ics23.VerifyMembership(ics23.IavlSpec, committedTreeRoot, proof, existenceProof.Key, existenceProof.Value) { 313 313 return &processResult{ 314 314 Code: 4307, 315 - Info: "invalid proof", 315 + Log: "invalid proof", 316 316 }, nil 317 317 } 318 318
+2 -2
abciapp/tx_create_plc_op.go
··· 31 31 if err != nil { 32 32 return &processResult{ 33 33 Code: 4000, 34 - Info: err.Error(), 34 + Log: err.Error(), 35 35 }, nil 36 36 } 37 37 ··· 61 61 if code, ok := plc.InvalidOperationErrorCode(err); ok { 62 62 return &processResult{ 63 63 Code: code, 64 - Info: err.Error(), 64 + Log: err.Error(), 65 65 }, nil 66 66 } 67 67 return nil, stacktrace.Propagate(err, "internal error")
+9 -9
abciapp/tx_import.go
··· 31 31 if err != nil { 32 32 return &processResult{ 33 33 Code: 4000, 34 - Info: err.Error(), 34 + Log: err.Error(), 35 35 }, nil 36 36 } 37 37 ··· 48 48 if err != nil || parsed.Scheme != "https" { 49 49 return &processResult{ 50 50 Code: 4100, 51 - Info: "Malformed Authoritative PLC URL", 51 + Log: "Malformed Authoritative PLC URL", 52 52 }, nil 53 53 } 54 54 } ··· 98 98 if err != nil { 99 99 return &processResult{ 100 100 Code: 4000, 101 - Info: err.Error(), 101 + Log: err.Error(), 102 102 }, nil 103 103 } 104 104 ··· 110 110 if expectedPlcUrl != tx.Arguments.PLCURL || expectedPlcUrl == "" { 111 111 return &processResult{ 112 112 Code: 4110, 113 - Info: "Unexpected Authoritative PLC URL", 113 + Log: "Unexpected Authoritative PLC URL", 114 114 }, nil 115 115 } 116 116 ··· 124 124 if expectedCursor != tx.Arguments.Cursor { 125 125 return &processResult{ 126 126 Code: 4111, 127 - Info: "Unexpected import cursor", 127 + Log: "Unexpected import cursor", 128 128 }, nil 129 129 } 130 130 131 131 if tx.Arguments.Count > MaxOpsPerImportTx || tx.Arguments.Count == 0 { 132 132 return &processResult{ 133 133 Code: 4112, 134 - Info: "Unexpected import count", 134 + Log: "Unexpected import count", 135 135 }, nil 136 136 } 137 137 ··· 139 139 if err != nil { 140 140 return &processResult{ 141 141 Code: 4113, 142 - Info: "Failure to obtain authoritative operations", 142 + Log: "Failure to obtain authoritative operations", 143 143 }, nil 144 144 } 145 145 146 146 if uint64(len(operations)) < tx.Arguments.Count { 147 147 return &processResult{ 148 148 Code: 4114, 149 - Info: "Unexpected import count", 149 + Log: "Unexpected import count", 150 150 }, nil 151 151 } 152 152 ··· 158 158 if hex.EncodeToString(expectedHashBytes) != tx.Arguments.Hash { 159 159 return &processResult{ 160 160 Code: 4115, 161 - Info: "Unexpected import hash", 161 + Log: "Unexpected import hash", 162 162 }, nil 163 163 } 164 164