mirror of
https://github.com/PeernetOfficial/Cmd.git
synced 2026-07-17 02:47:52 +01:00
New API functions #1:
/blockchain/self/header /blockchain/self/append /blockchain/self/read /blockchain/self/add/file The local blockchain now works, files metadata can be added and blocks read and decoded successfully.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
log.txt
|
||||
*debug_bin
|
||||
*.yaml
|
||||
.vscode
|
||||
self.*
|
||||
180
API.go
180
API.go
@@ -11,8 +11,10 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core"
|
||||
@@ -36,6 +38,10 @@ func startAPI() {
|
||||
router.HandleFunc("/status", apiStatus).Methods("GET")
|
||||
router.HandleFunc("/peer/self", apiPeerSelf).Methods("GET")
|
||||
router.HandleFunc("/console", apiConsole).Methods("GET")
|
||||
router.HandleFunc("/blockchain/self/header", apiBlockchainSelfHeader).Methods("GET")
|
||||
router.HandleFunc("/blockchain/self/append", apiBlockchainSelfAppend).Methods("POST")
|
||||
router.HandleFunc("/blockchain/self/read", apiBlockchainSelfRead).Methods("GET")
|
||||
router.HandleFunc("/blockchain/self/add/file", apiBlockchainSelfAddFile).Methods("POST")
|
||||
|
||||
for _, listen := range config.APIListen {
|
||||
go startWebServer(listen, config.APIUseSSL, config.APICertificateFile, config.APICertificateKey, router, "API", parseDuration(config.APITimeoutRead), parseDuration(config.APITimeoutWrite))
|
||||
@@ -85,6 +91,23 @@ func apiEncodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (er
|
||||
return err
|
||||
}
|
||||
|
||||
// apiDecodeJSON decodes input JSON data server side sent either via GET or POST. It does not limit the maximum amount to read.
|
||||
// In case of error it will automatically send an error to the client.
|
||||
func apiDecodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) {
|
||||
if r.Body == nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return errors.New("no data")
|
||||
}
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(data)
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiTest(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
@@ -94,6 +117,9 @@ type apiResponseStatus struct {
|
||||
Status int `json:"status"` // Status code: 0 = Ok.
|
||||
IsConnected bool `json:"isconnected"` // Whether connected to Peernet.
|
||||
CountPeerList int `json:"countpeerlist"` // Count of peers in the peer list. Note that this contains peers that are considered inactive, but have not yet been removed from the list.
|
||||
CountNetwork int `json:"countnetwork"` // Count of total peers in the network.
|
||||
// This is usually a higher number than CountPeerList, which just represents the current number of connected peers.
|
||||
// The CountNetwork number is going to be queried from root peers which may or may not have a limited view.
|
||||
}
|
||||
|
||||
/* apiStatus returns the current connectivity status to the network
|
||||
@@ -102,6 +128,7 @@ Result: 200 with JSON structure Status
|
||||
*/
|
||||
func apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
status := apiResponseStatus{Status: 0, CountPeerList: core.PeerlistCount()}
|
||||
status.CountNetwork = status.CountPeerList // For now always same as CountPeerList, until native Statistics message to root peers is available.
|
||||
|
||||
// Connected: If at leat 2 peers.
|
||||
// This metric needs to be improved in the future, as root peers never disconnect.
|
||||
@@ -188,3 +215,156 @@ func apiConsole(w http.ResponseWriter, r *http.Request) {
|
||||
bufferR.Write(message)
|
||||
}
|
||||
}
|
||||
|
||||
type apiBlockchainHeader struct {
|
||||
PeerID string `json:"peerid"` // Peer ID hex encoded.
|
||||
Version uint64 `json:"version"` // Current version number of the blockchain.
|
||||
Height uint64 `json:"height"` // Height of the blockchain (number of blocks). If 0, no data exists.
|
||||
}
|
||||
|
||||
/*
|
||||
apiBlockchainSelfHeader returns the current blockchain header information
|
||||
|
||||
Request: GET /blockchain/self/header
|
||||
Result: 200 with JSON structure apiResponsePeerSelf
|
||||
*/
|
||||
func apiBlockchainSelfHeader(w http.ResponseWriter, r *http.Request) {
|
||||
publicKey, height, version := core.UserBlockchainHeader()
|
||||
|
||||
apiEncodeJSON(w, r, apiBlockchainHeader{Version: version, Height: height, PeerID: hex.EncodeToString(publicKey.SerializeCompressed())})
|
||||
}
|
||||
|
||||
type apiBlockRecordRaw struct {
|
||||
Type uint8 `json:"type"` // Record Type. See core.RecordTypeX.
|
||||
Data []byte `json:"data"` // Data according to the type.
|
||||
}
|
||||
|
||||
// apiBlockchainBlockRaw contains a raw block of the blockchain via API
|
||||
type apiBlockchainBlockRaw struct {
|
||||
Records []apiBlockRecordRaw `json:"records"` // Block records in encoded raw format.
|
||||
}
|
||||
|
||||
type apiBlockchainBlockStatus struct {
|
||||
Status int `json:"status"` // Status: 0 = Success, 1 = Error invalid data
|
||||
Height uint64 `json:"height"` // New height of the blockchain (number of blocks).
|
||||
}
|
||||
|
||||
/*
|
||||
apiBlockchainSelfAppend appends a block to the blockchain. This is a low-level function for already encoded blocks.
|
||||
Do not use this function. Adding invalid data to the blockchain may corrupt it which might result in blacklisting by other peers.
|
||||
|
||||
Request: POST /blockchain/self/append with JSON structure apiBlockchainBlockRaw
|
||||
Response: 200 with JSON structure apiBlockchainBlockStatus
|
||||
*/
|
||||
func apiBlockchainSelfAppend(w http.ResponseWriter, r *http.Request) {
|
||||
var input apiBlockchainBlockRaw
|
||||
if err := apiDecodeJSON(w, r, &input); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var records []core.BlockRecordRaw
|
||||
|
||||
for _, record := range input.Records {
|
||||
records = append(records, core.BlockRecordRaw{Type: record.Type, Data: record.Data})
|
||||
}
|
||||
|
||||
newHeight, status := core.UserBlockchainAppend(records)
|
||||
|
||||
apiEncodeJSON(w, r, apiBlockchainBlockStatus{Status: status, Height: newHeight})
|
||||
}
|
||||
|
||||
type apiBlockchainBlock struct {
|
||||
Status int `json:"status"` // Status: 0 = Success, 1 = Error block not found, 2 = Error block encoding (indicates that the blockchain is corrupt)
|
||||
PeerID string `json:"peerid"` // Peer ID hex encoded.
|
||||
LastBlockHash []byte `json:"lastblockhash"` // Hash of the last block. Blake3.
|
||||
BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version
|
||||
Number uint64 `json:"blocknumber"` // Block number
|
||||
RecordsRaw []apiBlockRecordRaw `json:"recordsraw"` // Block records raw. Successfully decoded records are parsed into the below fields.
|
||||
Decoded struct {
|
||||
User apiBlockRecordUser `json:"user"` // User details
|
||||
Files []apiBlockRecordFile `json:"files"` // Files
|
||||
} `json:"decoded"`
|
||||
}
|
||||
|
||||
// apiBlockRecordUser specifies user information
|
||||
type apiBlockRecordUser struct {
|
||||
NameValid bool `json:"namevalid"` // Whether the name is supplied in this structure.
|
||||
Name string `json:"name"` // Arbitrary name of the user.
|
||||
NameSanitized string `json:"namesanitized"` // Sanitized version of the name.
|
||||
}
|
||||
|
||||
// apiBlockRecordFile is the metadata of a file published on the blockchain
|
||||
type apiBlockRecordFile struct {
|
||||
Hash []byte `json:"hash"` // Blake3 hash of the file data
|
||||
Type uint8 `json:"type"` // Type (low-level)
|
||||
Format uint16 `json:"format"` // Format (high-level)
|
||||
Size uint64 `json:"size"` // Size of the file
|
||||
Directory string `json:"directory"` // Directory
|
||||
Name string `json:"name"` // File name
|
||||
}
|
||||
|
||||
/*
|
||||
apiBlockchainSelfRead reads a block and returns the decoded information.
|
||||
|
||||
Request: GET /blockchain/self/read?block=[number]
|
||||
Result: 200 with JSON structure apiBlockchainBlock
|
||||
*/
|
||||
func apiBlockchainSelfRead(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
blockN, err := strconv.Atoi(r.Form.Get("block"))
|
||||
if err != nil || blockN < 0 {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
block, status := core.UserBlockchainRead(uint64(blockN))
|
||||
result := apiBlockchainBlock{Status: status}
|
||||
|
||||
if status == 0 {
|
||||
for _, record := range block.RecordsRaw {
|
||||
result.RecordsRaw = append(result.RecordsRaw, apiBlockRecordRaw{Type: record.Type, Data: record.Data})
|
||||
}
|
||||
|
||||
result.PeerID = hex.EncodeToString(block.OwnerPublicKey.SerializeCompressed())
|
||||
|
||||
if block.User.Valid {
|
||||
result.Decoded.User.NameValid = true
|
||||
result.Decoded.User.Name = block.User.Name
|
||||
result.Decoded.User.NameSanitized = block.User.NameS
|
||||
}
|
||||
|
||||
for _, file := range block.Files {
|
||||
result.Decoded.Files = append(result.Decoded.Files, apiBlockRecordFile{Hash: file.Hash, Type: file.Type, Format: file.Format, Size: file.Size, Directory: file.Directory, Name: file.Name})
|
||||
}
|
||||
}
|
||||
|
||||
apiEncodeJSON(w, r, result)
|
||||
}
|
||||
|
||||
// apiBlockAddFiles contains the file metadata to add to the blockchain
|
||||
type apiBlockAddFiles struct {
|
||||
Files []apiBlockRecordFile `json:"files"`
|
||||
}
|
||||
|
||||
/*
|
||||
apiBlockchainSelfAddFile adds a file with the provided information to the blockchain.
|
||||
|
||||
Request: POST /blockchain/self/add/file with JSON structure apiBlockAddFiles
|
||||
Response: 200 with JSON structure apiBlockchainBlockStatus
|
||||
*/
|
||||
func apiBlockchainSelfAddFile(w http.ResponseWriter, r *http.Request) {
|
||||
var input apiBlockAddFiles
|
||||
if err := apiDecodeJSON(w, r, &input); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var files []core.BlockRecordFile
|
||||
|
||||
for _, file := range input.Files {
|
||||
files = append(files, core.BlockRecordFile{Hash: file.Hash, Type: file.Type, Format: file.Format, Size: file.Size, Directory: file.Directory, Name: file.Name})
|
||||
}
|
||||
|
||||
newHeight, status := core.UserBlockchainAddFiles(files)
|
||||
|
||||
apiEncodeJSON(w, r, apiBlockchainBlockStatus{Status: status, Height: newHeight})
|
||||
}
|
||||
|
||||
125
API.md
125
API.md
@@ -14,7 +14,7 @@ The API is currently unauthenticated and intentionally provides direct access to
|
||||
|
||||
The API is still in development and endpoints are subject to change. The API should be currently only used for debugging and early phase development purposes.
|
||||
|
||||
## Configuartion
|
||||
## Configuration
|
||||
|
||||
The configuration file (default `Config.yaml`) contains settings for the API. To listen on `http://127.0.0.1:112/` add this line:
|
||||
|
||||
@@ -39,9 +39,10 @@ These are the functions provided by the API:
|
||||
/status/ws Starts a websocket to receive updates on operations immediately (push instead of pull)
|
||||
/console Console provides a websocket to send/receive internal commands
|
||||
|
||||
/blockchain/self/header Header of the self peers blockchain
|
||||
/blockchain/self/read Read the self peers blockchain
|
||||
/blockchain/self/append Add a record to the blockchain
|
||||
/blockchain/self/header Header of the blockchain
|
||||
/blockchain/self/append Append a block to the blockchain
|
||||
/blockchain/self/read Read a block of the blockchain
|
||||
/blockchain/self/add/file Add file to the blockchain
|
||||
```
|
||||
|
||||
The `/share` functions are providing high-level functionality to work with files. The `/blockchain` functions provide low-level functionality which is typically not needed.
|
||||
@@ -61,11 +62,12 @@ type apiResponseStatus struct {
|
||||
Status int `json:"status"` // Status code: 0 = Ok.
|
||||
IsConnected bool `json:"isconnected"` // Whether connected to Peernet.
|
||||
CountPeerList int `json:"countpeerlist"` // Count of peers in the peer list. Note that this contains peers that are considered inactive, but have not yet been removed from the list.
|
||||
CountNetwork int `json:"countnetwork"` // Count of total peers in the network.
|
||||
// This is usually a higher number than CountPeerList, which just represents the current number of connected peers.
|
||||
// The CountNetwork number is going to be queried from root peers which may or may not have a limited view into the network.
|
||||
}
|
||||
```
|
||||
|
||||
Example response data: Todo
|
||||
|
||||
## Self Information
|
||||
|
||||
This function returns information about the current peer.
|
||||
@@ -76,7 +78,7 @@ Request: GET /peer/self
|
||||
Response: 200 with JSON structure apiResponsePeerSelf
|
||||
```
|
||||
|
||||
The peer and node IDs are returned as hex encoded strings.
|
||||
The peer and node IDs are encoded as hex encoded strings.
|
||||
|
||||
```go
|
||||
type apiResponsePeerSelf struct {
|
||||
@@ -85,8 +87,6 @@ type apiResponsePeerSelf struct {
|
||||
}
|
||||
```
|
||||
|
||||
Example response data: Todo
|
||||
|
||||
## Console
|
||||
|
||||
The `/console` websocket allows to execute internal commands. This should be only used for debugging purposes by the end-user. The same input and output as raw text as via the command-line is provided through this endpoint.
|
||||
@@ -94,3 +94,110 @@ The `/console` websocket allows to execute internal commands. This should be onl
|
||||
```
|
||||
Request: ws://127.0.0.1:112/console
|
||||
```
|
||||
|
||||
## Blockchain Self Header
|
||||
|
||||
This function returns information about the current peer. It is not required that a peer has a blockchain. If no data is shared, there are no blocks. The blockchain does not formally have a header as each block has the same structure.
|
||||
|
||||
```
|
||||
Request: GET /blockchain/self/header
|
||||
|
||||
Response: 200 with JSON structure apiBlockchainHeader
|
||||
```
|
||||
|
||||
```go
|
||||
type apiBlockchainHeader struct {
|
||||
PeerID string `json:"peerid"` // Peer ID hex encoded.
|
||||
Version uint64 `json:"version"` // Current version number of the blockchain.
|
||||
Height uint64 `json:"height"` // Height of the blockchain (number of blocks). If 0, no data exists.
|
||||
}
|
||||
```
|
||||
|
||||
## Blockchain Append Block
|
||||
|
||||
This appends a block to the blockchain. This is a low-level function for already encoded blocks.
|
||||
Do not use this function. Adding invalid data to the blockchain may corrupt it which subsequently might result in blacklisting by other peers.
|
||||
|
||||
```
|
||||
Request: POST /blockchain/self/append with JSON structure apiBlockchainBlockRaw
|
||||
|
||||
Response: 200 with JSON structure apiBlockchainBlockStatus
|
||||
```
|
||||
|
||||
```go
|
||||
type apiBlockRecordRaw struct {
|
||||
Type uint8 `json:"type"` // Record Type. See core.RecordTypeX.
|
||||
Data []byte `json:"data"` // Data according to the type.
|
||||
}
|
||||
|
||||
type apiBlockchainBlockRaw struct {
|
||||
Records []apiBlockRecordRaw `json:"records"` // Block records in encoded raw format.
|
||||
}
|
||||
|
||||
type apiBlockchainBlockStatus struct {
|
||||
Status int `json:"status"` // Status: 0 = Success, 1 = Error invalid data
|
||||
Version uint64 `json:"version"` // Current version number of the blockchain.
|
||||
Height uint64 `json:"height"` // Height of the blockchain (number of blocks).
|
||||
}
|
||||
```
|
||||
|
||||
## Blockchain Read Block
|
||||
|
||||
This reads a block of the current peer.
|
||||
|
||||
```
|
||||
Request: GET /blockchain/self/read?block=[number]
|
||||
|
||||
Response: 200 with JSON structure apiBlockchainBlock
|
||||
```
|
||||
|
||||
```go
|
||||
type apiBlockchainBlock struct {
|
||||
Status int `json:"status"` // Status: 0 = Success, 1 = Error block not found, 2 = Error block encoding (indicates that the blockchain is corrupt)
|
||||
PeerID string `json:"peerid"` // Peer ID hex encoded.
|
||||
LastBlockHash []byte `json:"lastblockhash"` // Hash of the last block. Blake3.
|
||||
BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version
|
||||
Number uint64 `json:"blocknumber"` // Block number
|
||||
RecordsRaw []apiBlockRecordRaw `json:"recordsraw"` // Block records raw. Successfully decoded records are parsed into the below fields.
|
||||
decoded struct {
|
||||
User apiBlockRecordUser `json:"user"` // User details
|
||||
Files []apiBlockRecordFile `json:"files"` // Files
|
||||
} `json:"decoded"`
|
||||
}
|
||||
|
||||
type apiBlockRecordUser struct {
|
||||
NameValid bool `json:"namevalid"` // Whether the name is supplied in this structure.
|
||||
Name string `json:"name"` // Arbitrary name of the user.
|
||||
NameSanitized string `json:"namesanitized"` // Sanitized version of the name.
|
||||
}
|
||||
|
||||
type apiBlockRecordFile struct {
|
||||
Hash []byte // Hash of the file data
|
||||
Type uint8 // Type (low-level)
|
||||
Format uint16 // Format (high-level)
|
||||
Size uint64 // Size of the file
|
||||
Directory string // Directory
|
||||
Name string // File name
|
||||
}
|
||||
```
|
||||
|
||||
## Blockchain Add File
|
||||
|
||||
This adds a file with the provided information to the blockchain.
|
||||
|
||||
```
|
||||
Request: POST /blockchain/self/add/file with JSON structure apiBlockAddFiles
|
||||
|
||||
Response: 200 with JSON structure apiBlockchainBlockStatus
|
||||
```
|
||||
|
||||
```go
|
||||
type apiBlockAddFiles struct {
|
||||
Files []apiBlockRecordFile `json:"files"`
|
||||
}
|
||||
|
||||
type apiBlockchainBlockStatus struct {
|
||||
Status int `json:"status"` // Status: 0 = Success, 1 = Error invalid data
|
||||
Height uint64 `json:"height"` // New height of the blockchain (number of blocks).
|
||||
}
|
||||
```
|
||||
|
||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/PeernetOfficial/Cmd
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/PeernetOfficial/core v0.0.0-20210731152914-2937154c8d7b
|
||||
github.com/PeernetOfficial/core v0.0.0-20210819021308-a2420b8468b1
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20210625194946-86a17263b0ff
|
||||
github.com/gorilla/mux v1.8.1-0.20200912192056-d07530f46e1e
|
||||
github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,6 +1,8 @@
|
||||
github.com/PeernetOfficial/core v0.0.0-20210731152914-2937154c8d7b h1:y/Afz6M1vlLpn5nmZmHyK0Tze7hOl9N7p43HfTb578I=
|
||||
github.com/PeernetOfficial/core v0.0.0-20210731152914-2937154c8d7b/go.mod h1:9aIe6MS6tefJnfmyEZFqaNF4NIeq7wJutV7EU+uowps=
|
||||
github.com/PeernetOfficial/core v0.0.0-20210819021308-a2420b8468b1 h1:FTI8IWp3NN7ypWz+nTcERpFlBlE6PVzt1bhSTfzPd2Q=
|
||||
github.com/PeernetOfficial/core v0.0.0-20210819021308-a2420b8468b1/go.mod h1:upfaI2iJJ7lyH+UtOLU7t6oVcLO7PrDF7SsSUWJJ/OY=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
|
||||
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20210625194946-86a17263b0ff h1:7aWbvIwkXchSCIWgobzJ+a6l87s6WHkJ78sZqxSpQCk=
|
||||
|
||||
Reference in New Issue
Block a user