added new REST API for config file

This commit is contained in:
2023-07-27 18:41:57 +01:00
parent 50085c6b11
commit 23397e3d4a
4 changed files with 167 additions and 163 deletions

View File

@@ -7,151 +7,136 @@ Author: Peter Kleissner
package core package core
import ( import (
"github.com/PeernetOfficial/core/blockchain" "github.com/PeernetOfficial/core/blockchain"
"github.com/PeernetOfficial/core/protocol" "github.com/PeernetOfficial/core/protocol"
"github.com/enfipy/locker" "github.com/enfipy/locker"
) )
// The blockchain cache stores blockchains. // The blockchain cache stores blockchains.
type BlockchainCache struct { type BlockchainCache struct {
BlockchainDirectory string // The directory for storing blockchains in a key-value store. BlockchainDirectory string // The directory for storing blockchains in a key-value store.
MaxBlockSize uint64 // Max block size to accept. MaxBlockSize uint64 // Max block size to accept.
MaxBlockCount uint64 // Max block count to cache per peer. MaxBlockCount uint64 // Max block count to cache per peer.
LimitTotalRecords uint64 // Max count of blocks and header in total to keep across all blockchains. 0 = unlimited. Max Records * Max Block Size = Size Limit. LimitTotalRecords uint64 // Max count of blocks and header in total to keep across all blockchains. 0 = unlimited. Max Records * Max Block Size = Size Limit.
ReadOnly bool // Whether the cache is read only. ReadOnly bool // Whether the cache is read only.
Store *blockchain.MultiStore Store *blockchain.MultiStore
peerLock *locker.Locker peerLock *locker.Locker
backend *Backend backend *Backend
} }
func (backend *Backend) initBlockchainCache() { func (backend *Backend) initBlockchainCache() {
if backend.Config.BlockchainGlobal == "" { if backend.Config.BlockchainGlobal == "" {
return return
} }
backend.GlobalBlockchainCache = &BlockchainCache{ backend.GlobalBlockchainCache = &BlockchainCache{
backend: backend, backend: backend,
BlockchainDirectory: backend.Config.BlockchainGlobal, BlockchainDirectory: backend.Config.BlockchainGlobal,
MaxBlockSize: backend.Config.CacheMaxBlockSize, MaxBlockSize: backend.Config.CacheMaxBlockSize,
MaxBlockCount: backend.Config.CacheMaxBlockCount, MaxBlockCount: backend.Config.CacheMaxBlockCount,
LimitTotalRecords: backend.Config.LimitTotalRecords, LimitTotalRecords: backend.Config.LimitTotalRecords,
} }
var err error var err error
backend.GlobalBlockchainCache.Store, err = blockchain.InitMultiStore(backend.Config.BlockchainGlobal) backend.GlobalBlockchainCache.Store, err = blockchain.InitMultiStore(backend.Config.BlockchainGlobal)
if err != nil { if err != nil {
backend.LogError("initBlockchainCache", "initializing database '%s': %s", backend.Config.BlockchainGlobal, err.Error()) backend.LogError("initBlockchainCache", "initializing database '%s': %s", backend.Config.BlockchainGlobal, err.Error())
return return
} }
backend.GlobalBlockchainCache.peerLock = locker.Initialize() backend.GlobalBlockchainCache.peerLock = locker.Initialize()
// Set the blockchain cache to read-only if the record limit is reached. // Set the blockchain cache to read-only if the record limit is reached.
if backend.Config.LimitTotalRecords > 0 && backend.GlobalBlockchainCache.Store.Database.Count() >= backend.Config.LimitTotalRecords { if backend.Config.LimitTotalRecords > 0 && backend.GlobalBlockchainCache.Store.Database.Count() >= backend.Config.LimitTotalRecords {
backend.GlobalBlockchainCache.ReadOnly = true backend.GlobalBlockchainCache.ReadOnly = true
} }
backend.GlobalBlockchainCache.Store.FilterStatisticUpdate = backend.Filters.GlobalBlockchainCacheStatistic backend.GlobalBlockchainCache.Store.FilterStatisticUpdate = backend.Filters.GlobalBlockchainCacheStatistic
backend.GlobalBlockchainCache.Store.FilterBlockchainDelete = backend.Filters.GlobalBlockchainCacheDelete backend.GlobalBlockchainCache.Store.FilterBlockchainDelete = backend.Filters.GlobalBlockchainCacheDelete
} }
// SeenBlockchainVersion shall be called with information about another peer's blockchain. // SeenBlockchainVersion shall be called with information about another peer's blockchain.
// If the reported version number is newer, all existing blocks are immediately deleted. // If the reported version number is newer, all existing blocks are immediately deleted.
func (cache *BlockchainCache) SeenBlockchainVersion(peer *PeerInfo) { func (cache *BlockchainCache) SeenBlockchainVersion(peer *PeerInfo) {
cache.peerLock.Lock(string(peer.PublicKey.SerializeCompressed())) cache.peerLock.Lock(string(peer.PublicKey.SerializeCompressed()))
defer cache.peerLock.Unlock(string(peer.PublicKey.SerializeCompressed())) defer cache.peerLock.Unlock(string(peer.PublicKey.SerializeCompressed()))
// intermediate function to download and process blocks // intermediate function to download and process blocks
downloadAndProcessBlocks := func(peer *PeerInfo, header *blockchain.MultiBlockchainHeader, offset, limit uint64) { downloadAndProcessBlocks := func(peer *PeerInfo, header *blockchain.MultiBlockchainHeader, offset, limit uint64) {
if limit > cache.MaxBlockCount { if limit > cache.MaxBlockCount {
limit = cache.MaxBlockCount limit = cache.MaxBlockCount
} }
peer.BlockDownload(peer.PublicKey, cache.MaxBlockCount, cache.MaxBlockSize, []protocol.BlockRange{{Offset: offset, Limit: limit}}, func(data []byte, targetBlock protocol.BlockRange, blockSize uint64, availability uint8) { peer.BlockDownload(peer.PublicKey, cache.MaxBlockCount, cache.MaxBlockSize, []protocol.BlockRange{{Offset: offset, Limit: limit}}, func(data []byte, targetBlock protocol.BlockRange, blockSize uint64, availability uint8) {
if availability != protocol.GetBlockStatusAvailable { if availability != protocol.GetBlockStatusAvailable {
return return
} }
if decoded, _ := cache.Store.IngestBlock(header, targetBlock.Offset, data, true); decoded != nil { if decoded, _ := cache.Store.IngestBlock(header, targetBlock.Offset, data, true); decoded != nil {
// index it for search // index it for search
cache.backend.SearchIndex.IndexNewBlockDecoded(peer.PublicKey, peer.BlockchainVersion, targetBlock.Offset, decoded.RecordsDecoded) cache.backend.SearchIndex.IndexNewBlockDecoded(peer.PublicKey, peer.BlockchainVersion, targetBlock.Offset, decoded.RecordsDecoded)
} }
}) })
} }
// get the old header // get the old header
header, status, err := cache.Store.AssessBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight) header, status, err := cache.Store.AssessBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight)
if err != nil { if err != nil {
return return
} }
switch status { switch status {
case blockchain.MultiStatusEqual: case blockchain.MultiStatusEqual:
return return
case blockchain.MultiStatusInvalidRemote: case blockchain.MultiStatusInvalidRemote:
cache.Store.DeleteBlockchain(header) cache.Store.DeleteBlockchain(header)
cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey) cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey)
case blockchain.MultiStatusHeaderNA: case blockchain.MultiStatusHeaderNA:
if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil { if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil {
return return
} }
downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight) downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight)
case blockchain.MultiStatusNewVersion: case blockchain.MultiStatusNewVersion:
// delete existing data first, then create it new // delete existing data first, then create it new
cache.Store.DeleteBlockchain(header) cache.Store.DeleteBlockchain(header)
cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey) cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey)
if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil { if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil {
return return
} }
downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight) downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight)
case blockchain.MultiStatusNewBlocks: case blockchain.MultiStatusNewBlocks:
//offset := header.Height offset := header.Height
//limit := peer.BlockchainHeight - header.Height limit := peer.BlockchainHeight - header.Height
// header.Height = peer.BlockchainHeight
//header.Height = peer.BlockchainHeight downloadAndProcessBlocks(peer, header, offset, limit)
//
//fmt.Println("new peer height")
//
//downloadAndProcessBlocks(peer, header, offset, limit)
// delete existing data first, then create it new
// Temporary Fix to force update the public key
cache.Store.DeleteBlockchain(header)
cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey) }
if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil { if cache.LimitTotalRecords > 0 {
return // Bug: This code is currently never reached if ReadOnly is true.
} cache.ReadOnly = cache.Store.Database.Count() >= cache.LimitTotalRecords
}
downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight)
}
if cache.LimitTotalRecords > 0 {
// Bug: This code is currently never reached if ReadOnly is true.
cache.ReadOnly = cache.Store.Database.Count() >= cache.LimitTotalRecords
}
} }
// remoteBlockchainUpdate shall be called to indicate a potential update of the remotes blockchain. // remoteBlockchainUpdate shall be called to indicate a potential update of the remotes blockchain.
// It will use the blockchain version and height to update the data lake as appropriate. // It will use the blockchain version and height to update the data lake as appropriate.
// This function is called in the Go routine of the packet worker and therefore must not stall. // This function is called in the Go routine of the packet worker and therefore must not stall.
func (peer *PeerInfo) remoteBlockchainUpdate() { func (peer *PeerInfo) remoteBlockchainUpdate() {
if peer.Backend.GlobalBlockchainCache == nil || peer.Backend.GlobalBlockchainCache.ReadOnly || peer.BlockchainVersion == 0 && peer.BlockchainHeight == 0 { if peer.Backend.GlobalBlockchainCache == nil || peer.Backend.GlobalBlockchainCache.ReadOnly || peer.BlockchainVersion == 0 && peer.BlockchainHeight == 0 {
return return
} }
// TODO: This entire function should be instead a non-blocking message via a buffer channel. // TODO: This entire function should be instead a non-blocking message via a buffer channel.
go peer.Backend.GlobalBlockchainCache.SeenBlockchainVersion(peer) go peer.Backend.GlobalBlockchainCache.SeenBlockchainVersion(peer)
} }

View File

@@ -43,6 +43,6 @@ LocalFirewall: false # Indicates that a local firewall may drop unsolicited i
PortForward: 0 # Default not set. PortForward: 0 # Default not set.
# Global blockchain cache limits # Global blockchain cache limits
CacheMaxBlockSize: 4096 # Max block size to accept in bytes. CacheMaxBlockSize: 50096 # Max block size to accept in bytes.
CacheMaxBlockCount: 256 # Max block count to cache per peer. CacheMaxBlockCount: 256 # Max block count to cache per peer.
LimitTotalRecords: 0 # Record count limit. 0 = unlimited. Max Records * Max Block Size = Size Limit. LimitTotalRecords: 0 # Record count limit. 0 = unlimited. Max Records * Max Block Size = Size Limit.

View File

@@ -83,6 +83,7 @@ func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, Certifi
api.Router.HandleFunc("/test", apiTest).Methods("GET") api.Router.HandleFunc("/test", apiTest).Methods("GET")
api.Router.HandleFunc("/status", api.apiStatus).Methods("GET") api.Router.HandleFunc("/status", api.apiStatus).Methods("GET")
api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET") api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET")
api.Router.HandleFunc("/status/config", api.apiStatusConfig).Methods("GET")
api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET") api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET")
api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET") api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET")
api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET") api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET")

View File

@@ -7,24 +7,24 @@ Author: Peter Kleissner
package webapi package webapi
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
) )
func apiTest(w http.ResponseWriter, r *http.Request) { func apiTest(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte("ok")) w.Write([]byte("ok"))
} }
type apiResponseStatus struct { type apiResponseStatus struct {
Status int `json:"status"` // Status code: 0 = Ok. Status int `json:"status"` // Status code: 0 = Ok.
IsConnected bool `json:"isconnected"` // Whether connected to Peernet. 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. 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. 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. // 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. // The CountNetwork number is going to be queried from root peers which may or may not have a limited view.
} }
/* /*
@@ -33,20 +33,20 @@ Request: GET /status
Result: 200 with JSON structure Status Result: 200 with JSON structure Status
*/ */
func (api *WebapiInstance) apiStatus(w http.ResponseWriter, r *http.Request) { func (api *WebapiInstance) apiStatus(w http.ResponseWriter, r *http.Request) {
status := apiResponseStatus{Status: 0, CountPeerList: api.Backend.PeerlistCount()} status := apiResponseStatus{Status: 0, CountPeerList: api.Backend.PeerlistCount()}
status.CountNetwork = status.CountPeerList // For now always same as CountPeerList, until native Statistics message to root peers is available. 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. // Connected: If at leat 2 peers.
// This metric needs to be improved in the future, as root peers never disconnect. // This metric needs to be improved in the future, as root peers never disconnect.
// Instead, the core should keep a count of "active peers". // Instead, the core should keep a count of "active peers".
status.IsConnected = status.CountPeerList >= 2 status.IsConnected = status.CountPeerList >= 2
EncodeJSON(api.Backend, w, r, status) EncodeJSON(api.Backend, w, r, status)
} }
type apiResponsePeerSelf struct { type apiResponsePeerSelf struct {
PeerID string `json:"peerid"` // Peer ID. This is derived from the public in compressed form. PeerID string `json:"peerid"` // Peer ID. This is derived from the public in compressed form.
NodeID string `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT. NodeID string `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT.
} }
/* /*
@@ -55,13 +55,13 @@ Request: GET /account/info
Result: 200 with JSON structure apiResponsePeerSelf Result: 200 with JSON structure apiResponsePeerSelf
*/ */
func (api *WebapiInstance) apiAccountInfo(w http.ResponseWriter, r *http.Request) { func (api *WebapiInstance) apiAccountInfo(w http.ResponseWriter, r *http.Request) {
response := apiResponsePeerSelf{} response := apiResponsePeerSelf{}
response.NodeID = hex.EncodeToString(api.Backend.SelfNodeID()) response.NodeID = hex.EncodeToString(api.Backend.SelfNodeID())
_, publicKey := api.Backend.ExportPrivateKey() _, publicKey := api.Backend.ExportPrivateKey()
response.PeerID = hex.EncodeToString(publicKey.SerializeCompressed()) response.PeerID = hex.EncodeToString(publicKey.SerializeCompressed())
EncodeJSON(api.Backend, w, r, response) EncodeJSON(api.Backend, w, r, response)
} }
/* /*
@@ -72,15 +72,15 @@ Result: 204 if the user choses not to delete the account
200 if successfully deleted 200 if successfully deleted
*/ */
func (api *WebapiInstance) apiAccountDelete(w http.ResponseWriter, r *http.Request) { func (api *WebapiInstance) apiAccountDelete(w http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
if confirm, _ := strconv.ParseBool(r.Form.Get("confirm")); !confirm { if confirm, _ := strconv.ParseBool(r.Form.Get("confirm")); !confirm {
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
return return
} }
api.Backend.DeleteAccount() api.Backend.DeleteAccount()
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
/* /*
@@ -92,35 +92,53 @@ Request: GET /status/peers
Result: 200 with JSON array apiResponsePeerInfo Result: 200 with JSON array apiResponsePeerInfo
*/ */
func (api *WebapiInstance) apiStatusPeers(w http.ResponseWriter, r *http.Request) { func (api *WebapiInstance) apiStatusPeers(w http.ResponseWriter, r *http.Request) {
var peers []apiResponsePeerInfo var peers []apiResponsePeerInfo
// query all nodes // query all nodes
for _, peer := range api.Backend.PeerlistGet() { for _, peer := range api.Backend.PeerlistGet() {
peerInfo := apiResponsePeerInfo{ peerInfo := apiResponsePeerInfo{
PeerID: peer.PublicKey.SerializeCompressed(), PeerID: peer.PublicKey.SerializeCompressed(),
NodeID: peer.NodeID, NodeID: peer.NodeID,
UserAgent: peer.UserAgent, UserAgent: peer.UserAgent,
IsRoot: peer.IsRootPeer, IsRoot: peer.IsRootPeer,
BlockchainHeight: peer.BlockchainHeight, BlockchainHeight: peer.BlockchainHeight,
BlockchainVersion: peer.BlockchainVersion, BlockchainVersion: peer.BlockchainVersion,
} }
if latitude, longitude, valid := api.Peer2GeoIP(peer); valid { if latitude, longitude, valid := api.Peer2GeoIP(peer); valid {
peerInfo.GeoIP = fmt.Sprintf("%.4f", latitude) + "," + fmt.Sprintf("%.4f", longitude) peerInfo.GeoIP = fmt.Sprintf("%.4f", latitude) + "," + fmt.Sprintf("%.4f", longitude)
} }
peers = append(peers, peerInfo) peers = append(peers, peerInfo)
} }
EncodeJSON(api.Backend, w, r, peers) EncodeJSON(api.Backend, w, r, peers)
} }
type apiResponsePeerInfo struct { type apiResponsePeerInfo struct {
PeerID []byte `json:"peerid"` // Peer ID. This is derived from the public in compressed form. PeerID []byte `json:"peerid"` // Peer ID. This is derived from the public in compressed form.
NodeID []byte `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT. NodeID []byte `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT.
GeoIP string `json:"geoip"` // GeoIP location as "Latitude,Longitude" CSV format. Empty if location not available. GeoIP string `json:"geoip"` // GeoIP location as "Latitude,Longitude" CSV format. Empty if location not available.
UserAgent string `json:"useragent"` // User Agent. UserAgent string `json:"useragent"` // User Agent.
IsRoot bool `json:"isroot"` // If the peer is a root peer. IsRoot bool `json:"isroot"` // If the peer is a root peer.
BlockchainHeight uint64 `json:"blockchainheight"` // Blockchain height BlockchainHeight uint64 `json:"blockchainheight"` // Blockchain height
BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version
}
/*
apiConfigPeers return the appropriate config information of the current Peer
connected.
Request: GET /status/config
Result: 200 with JSON apiResponseConfig
*/
func (api *WebapiInstance) apiStatusConfig(w http.ResponseWriter, r *http.Request) {
var peer apiResponseConfig
peer.BlockSize = api.Backend.Config.CacheMaxBlockSize
EncodeJSON(api.Backend, w, r, peer)
}
type apiResponseConfig struct {
BlockSize uint64 `json:"blockSize"`
} }