A very experimental PLC implementation which uses BFT consensus for decentralization
at main 4.3 kB view raw
1package abciapp 2 3import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "net/http" 8 "net/url" 9 "time" 10 11 abcitypes "github.com/cometbft/cometbft/abci/types" 12 "github.com/palantir/stacktrace" 13 "github.com/ucarion/urlpath" 14 "tangled.org/gbl08ma.com/didplcbft/plc" 15 "tangled.org/gbl08ma.com/didplcbft/transaction" 16) 17 18// Info implements [types.Application]. 19func (d *DIDPLCApplication) Info(ctx context.Context, req *abcitypes.RequestInfo) (*abcitypes.ResponseInfo, error) { 20 return &abcitypes.ResponseInfo{ 21 Data: "", // TODO some status report as JSON? Information about CometBFT itself can be obtained from RequestInfo 22 Version: "0.1.1", 23 AppVersion: 0, // TODO, included in the header of every block. move these values to constants 24 LastBlockHeight: d.tree.Version(), 25 LastBlockAppHash: d.tree.Hash(), 26 }, nil 27} 28 29// Query implements [types.Application]. 30func (d *DIDPLCApplication) Query(ctx context.Context, req *abcitypes.RequestQuery) (*abcitypes.ResponseQuery, error) { 31 url, err := url.ParseRequestURI(req.Path) 32 if err != nil || url.Host != "" || url.Scheme != "" { 33 return &abcitypes.ResponseQuery{ 34 Code: 6000, 35 Info: "Invalid path", 36 }, nil 37 } 38 39 var readTx transaction.Read 40 height := d.tree.Version() 41 if req.Height != 0 { 42 height = req.Height 43 } 44 45 readTx, err = d.txFactory.ReadHeight(time.Now(), height) 46 if err != nil { 47 return &abcitypes.ResponseQuery{ 48 Code: 6001, 49 Info: "Unavailable height", 50 }, nil 51 } 52 53 handlers := []struct { 54 matcher urlpath.Path 55 handler func(match urlpath.Match) (*abcitypes.ResponseQuery, error) 56 }{ 57 { 58 matcher: urlpath.New("/plc/:did"), 59 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 60 did := match.Params["did"] 61 doc, err := d.plc.Resolve(ctx, readTx, did) 62 if err != nil { 63 switch { 64 case errors.Is(err, plc.ErrDIDNotFound): 65 return &abcitypes.ResponseQuery{ 66 Key: []byte(did), 67 Code: http.StatusNotFound, 68 Info: "DID not registered: " + did, 69 }, nil 70 case errors.Is(err, plc.ErrDIDGone): 71 return &abcitypes.ResponseQuery{ 72 Key: []byte(did), 73 Code: http.StatusGone, 74 Info: "DID not available: " + did, 75 }, nil 76 default: 77 return nil, stacktrace.Propagate(err, "") 78 } 79 } 80 81 docJSON, err := json.Marshal(doc) 82 if err != nil { 83 return nil, stacktrace.Propagate(err, "") 84 } 85 86 return &abcitypes.ResponseQuery{ 87 Key: []byte(did), 88 Value: []byte(docJSON), 89 Code: 0, 90 }, nil 91 }, 92 }, 93 { 94 matcher: urlpath.New("/plc/:did/log"), 95 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 96 return &abcitypes.ResponseQuery{ 97 Code: 1, 98 Info: "Not implemented", 99 }, nil 100 }, 101 }, 102 { 103 matcher: urlpath.New("/plc/:did/log/audit"), 104 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 105 return &abcitypes.ResponseQuery{ 106 Code: 1, 107 Info: "Not implemented", 108 }, nil 109 }, 110 }, 111 { 112 matcher: urlpath.New("/plc/:did/log/last"), 113 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 114 return &abcitypes.ResponseQuery{ 115 Code: 1, 116 Info: "Not implemented", 117 }, nil 118 }, 119 }, 120 { 121 matcher: urlpath.New("/plc/:did/data"), 122 handler: func(match urlpath.Match) (*abcitypes.ResponseQuery, error) { 123 did := match.Params["did"] 124 data, err := d.plc.Data(ctx, readTx, did) 125 if err != nil { 126 switch { 127 case errors.Is(err, plc.ErrDIDNotFound): 128 return &abcitypes.ResponseQuery{ 129 Key: []byte(did), 130 Code: http.StatusNotFound, 131 Info: "DID not registered: " + did, 132 }, nil 133 case errors.Is(err, plc.ErrDIDGone): 134 return &abcitypes.ResponseQuery{ 135 Key: []byte(did), 136 Code: http.StatusGone, 137 Info: "DID not available: " + did, 138 }, nil 139 default: 140 return nil, stacktrace.Propagate(err, "") 141 } 142 } 143 144 dataJSON, err := json.Marshal(&data) 145 if err != nil { 146 return nil, stacktrace.Propagate(err, "") 147 } 148 149 return &abcitypes.ResponseQuery{ 150 Key: []byte(did), 151 Value: []byte(dataJSON), 152 Code: 0, 153 }, nil 154 }, 155 }, 156 } 157 158 for _, h := range handlers { 159 if match, ok := h.matcher.Match(url.Path); ok { 160 return h.handler(match) 161 } 162 } 163 164 return &abcitypes.ResponseQuery{ 165 Code: 6000, 166 Info: "Invalid path", 167 }, nil 168 169}