Minor changes

This commit is contained in:
Dmitry Anderson 2024-11-24 15:04:52 +01:00
parent 4e044cdad9
commit 3c069df5c1
24 changed files with 580 additions and 177 deletions

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>

50
common/base_errors.go Normal file
View File

@ -0,0 +1,50 @@
package common
import (
"errors"
"github.com/matchsystems/werr"
"net/http"
)
var (
ErrNotImplemented = errors.New("not implemented yet")
ErrCanceled = errors.New("canceled")
ErrDeadlineExceeded = errors.New("deadline exceeded")
ErrRemoteServiceFailed = errors.New("remote service failed")
ErrInvalidAction = errors.New("invalid action")
ErrInvalidArgument = errors.New("invalid argument")
ErrOutOfRange = errors.New("out of range")
ErrPermissionDenied = errors.New("permission denied")
ErrUnauthenticated = errors.New("unauthenticated")
ErrEntityExists = errors.New("entity already exists")
ErrEntityNotFound = errors.New("entity not found")
ErrEntityOutdated = errors.New("entity outdated")
)
func GetHttpCode(err error) int {
err = werr.UnwrapAll(err)
switch {
case errors.Is(err, ErrNotImplemented):
return http.StatusNotImplemented
case errors.Is(err, ErrCanceled), errors.Is(err, ErrDeadlineExceeded):
return http.StatusRequestTimeout
case errors.Is(err, ErrRemoteServiceFailed):
return http.StatusServiceUnavailable
case errors.Is(err, ErrInvalidAction), errors.Is(err, ErrInvalidArgument), errors.Is(err, ErrOutOfRange):
return http.StatusBadRequest
case errors.Is(err, ErrPermissionDenied):
return http.StatusForbidden
case errors.Is(err, ErrUnauthenticated):
return http.StatusUnauthorized
case errors.Is(err, ErrEntityExists):
return http.StatusConflict
case errors.Is(err, ErrEntityNotFound):
return http.StatusNotFound
default:
return http.StatusInternalServerError
}
}

View File

@ -1,10 +1,138 @@
package common package common
import (
"bytes"
"crypto/sha512"
"encoding/binary"
"encoding/json"
"github.com/matchsystems/werr"
"io"
"time"
)
// BlockSecsDiff is the time in second that passes
// between blocks rotation
const BlockSecsDiff = 100
func GetBlockId(t time.Time) int64 {
// because number is int -- it will just cut off the floating point part
return (t.UTC().Unix() / BlockSecsDiff) * BlockSecsDiff
}
type BlockJson struct { type BlockJson struct {
Id int64 `json:"id"`
PrevHash string `json:"prev_hash"`
Hash string `json:"hash"`
TXs []json.RawMessage `json:"txs"`
IsFinished bool `json:"is_finished"`
ByteEncoding string `json:"byte_encoding"`
} }
type Block struct { type Block struct {
Id int64 Id int64
PrevHash []byte PrevHash []byte
Hash []byte Hash []byte
TXs []*Tx
IsFinished bool
}
func (block *Block) CalcHash() ([]byte, error) {
hash := sha512.New()
if err := binary.Write(hash, binary.BigEndian, block.Id); err != nil {
return nil, werr.Wrapf(err, "failed to write block id into hash buffer for block %d", block.Id)
}
hash.Write(block.PrevHash)
for _, tx := range block.TXs {
hash.Write(tx.Hash)
}
return hash.Sum(nil), nil
}
// ValidateHash calculates new block hash and compares it to current block.Hash
// returning is block hash is valid
func (block *Block) ValidateHash() bool {
hash, err := block.CalcHash()
if err != nil {
return false
}
return bytes.Equal(block.Hash, hash)
}
// ValidateTXs returns the number of invalid transactions
// and map of hashes : reasons why txs were invalid.
// If invalid count > 0 -- the whole block isn't legit
func (block *Block) ValidateTXs() (invalidCount int, invalidTXs map[string]string) {
invalidTXs = make(map[string]string)
for _, tx := range block.TXs {
if err := tx.Validate(); err != nil {
invalidTXs[tx.HashString()] = err.Error()
invalidCount++
}
}
return invalidCount, invalidTXs
}
func (block *Block) ToJSON(byteEncodingStr string) ([]byte, error) {
encoding, err := GetEncoding(byteEncodingStr)
if err != nil {
return nil, err
}
var txData json.RawMessage
txsData := make([]json.RawMessage, len(block.TXs))
for i, tx := range block.TXs {
if txData, err = tx.ToJSON(byteEncodingStr); err != nil {
return nil, werr.Wrapf(err, "error while converting transaction to JSON %d", i)
}
txsData[i] = txData
}
data := &BlockJson{
Id: block.Id,
PrevHash: encoding.EncodeToString(block.PrevHash),
Hash: encoding.EncodeToString(block.Hash),
TXs: txsData,
IsFinished: block.IsFinished,
}
jsonBytes, err := json.Marshal(data)
if err != nil {
return nil, werr.Wrapf(err, "error while marshaling")
}
return jsonBytes, err
}
func (block *BlockJson) Process() (*Block, error) {
var err error
txs := make([]*Tx, len(block.TXs))
for i, jsonTx := range block.TXs {
if txs[i], err = TxFromJSON(jsonTx); err != nil {
return nil, err
}
}
out := &Block{
Id: block.Id,
TXs: txs,
IsFinished: block.IsFinished,
}
encoding, err := GetEncoding(block.ByteEncoding)
if err != nil {
return nil, err
}
if out.Hash, err = encoding.DecodeString(block.Hash); err != nil {
return nil, werr.Wrapf(err, "error while decoding hash")
}
return out, nil
}
func BlockFromJSON(marshaledData []byte) (opts *Block, err error) {
data := &BlockJson{}
if err := json.Unmarshal(marshaledData, data); err != nil {
return nil, werr.Wrapf(err, "error while unmarshaling")
}
return data.Process()
}
func BlockFromJsonReader(r io.Reader) (opts *Block, err error) {
data := &BlockJson{}
if err := json.NewDecoder(r).Decode(&data); err != nil {
return nil, werr.Wrapf(err, "error while unmarshaling")
}
return data.Process()
} }

3
common/block_test.go Normal file
View File

@ -0,0 +1,3 @@
package common
// TODO

View File

@ -7,3 +7,18 @@ const BurningRate = 0.001
func CalcBurning(trxAmount float64) float64 { func CalcBurning(trxAmount float64) float64 {
return BurningRate * math.Sqrt(trxAmount) return BurningRate * math.Sqrt(trxAmount)
} }
// ValidateBurning doesn't return valid/invalid like usual Validate functions.
// Instead, it returns difference between expected and set values.
// Bcs of a differences in how programming languages might be implemented
// or approach numeric types (yes, JS, I'm about u) we might not get exactly the same burning amount,
// but still it should be a very close value.
// To get boolean value -- use ValidateBurningBool instead
func ValidateBurning(tx *Tx) float64 {
return CalcBurning(tx.Amount) - tx.AmountBurned
}
func ValidateBurningBool(tx *Tx) bool {
// Let's say, that before that count of digits we shouldn't have any mistake
return math.Abs(ValidateBurning(tx)) < 0.1e-10
}

View File

@ -3,7 +3,7 @@ package common
import ( import (
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "github.com/matchsystems/werr"
) )
const ( const (
@ -11,6 +11,7 @@ const (
EncodingRawStd = "encoring_row_std" EncodingRawStd = "encoring_row_std"
) )
// GetEncoding Returns base64 encoding based on the provided string
func GetEncoding(encoding string) (*base64.Encoding, error) { func GetEncoding(encoding string) (*base64.Encoding, error) {
switch encoding { switch encoding {
case EncodingRawUrl: case EncodingRawUrl:
@ -18,12 +19,11 @@ func GetEncoding(encoding string) (*base64.Encoding, error) {
case EncodingRawStd: case EncodingRawStd:
return base64.RawStdEncoding, nil return base64.RawStdEncoding, nil
default: default:
return nil, errors.New("unsupported encoding") return nil, werr.Wrapf(ErrInvalidArgument, "unknown encoding %s", encoding)
} }
} }
// float64ToBytes converts float64 to []byte // float64ToBytes converts float64 to []byte
// May be moved to nettools later
func float64ToBytes(f float64) []byte { func float64ToBytes(f float64) []byte {
buf := make([]byte, 8) buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(f)) binary.BigEndian.PutUint64(buf, uint64(f))

34
common/get_txs.go Normal file
View File

@ -0,0 +1,34 @@
package common
import "encoding/json"
const ()
type GetTxOpts struct {
TxHash []byte `param:"tx_hash"`
UserPubKey []byte `param:"user_pub_key"`
GetSent bool `param:"get_sent"`
GetReceived bool `param:"get_received"`
OrderAsc bool `param:"order_asc"`
Limit int `param:"limit"`
Offset int `param:"offset"`
Before int64 `param:"before"`
After int64 `param:"after"`
ByteEncoding string `param:"byte_encoding"`
}
type GetTxRespJson struct {
TXs []TxJson `json:"txs"`
PendingTXs []TxJson `json:"pending_txs"`
Count int `json:"count"`
ByteEncoding string `json:"byte_encoding"`
}
type GetTxResp struct {
TXs []Tx
PendingTXs []Tx
}
func (resp *GetTxResp) ToJson() ([]byte, error) {
return
}

5
common/node_info.go Normal file
View File

@ -0,0 +1,5 @@
package common
type NodeInfo struct {
NodeVersion string `json:"node_version"`
}

View File

@ -23,10 +23,6 @@ func GetTxHash(receiverPubKey []byte, message string, amount float64, blockId, t
return hash.Sum(nil) return hash.Sum(nil)
} }
func GetBlockId(t time.Time) int64 {
return (t.UTC().Unix() / 100) * 100
}
type NewTxOptsJson struct { type NewTxOptsJson struct {
Hash string `json:"hash"` // use function GetTxHash Hash string `json:"hash"` // use function GetTxHash
// (CreatedAt / 100) * 100 // (CreatedAt / 100) * 100
@ -103,7 +99,7 @@ func MakeNewTxOpts(senderPubKey, senderPrivateKey, receiverPubKey []byte,
now := time.Now().UTC() now := time.Now().UTC()
createdAt := now.Unix() createdAt := now.Unix()
createdAtNs := now.UnixNano() createdAtNs := now.UnixNano()
blockId := (createdAt / 100) * 100 blockId := (createdAt / BlockSecsDiff) * BlockSecsDiff
txHash := GetTxHash(receiverPubKey, msg, amount, blockId, createdAt, createdAtNs) txHash := GetTxHash(receiverPubKey, msg, amount, blockId, createdAt, createdAtNs)
sig := ed25519.Sign(senderPrivateKey, txHash) sig := ed25519.Sign(senderPrivateKey, txHash)
return &NewTxOpts{ return &NewTxOpts{
@ -121,7 +117,7 @@ func MakeNewTxOpts(senderPubKey, senderPrivateKey, receiverPubKey []byte,
} }
func (opts *NewTxOpts) ValidateBlockId() (valid bool) { func (opts *NewTxOpts) ValidateBlockId() (valid bool) {
return opts.BlockId == (opts.CreatedAt/100)*100 return opts.BlockId == (opts.CreatedAt/BlockSecsDiff)*BlockSecsDiff
} }
func (opts *NewTxOpts) ValidateHash() (valid bool) { func (opts *NewTxOpts) ValidateHash() (valid bool) {
return bytes.Equal(opts.Hash, GetTxHash( return bytes.Equal(opts.Hash, GetTxHash(
@ -131,17 +127,40 @@ func (opts *NewTxOpts) ValidateHash() (valid bool) {
func (opts *NewTxOpts) ValidateSignature() (valid bool) { func (opts *NewTxOpts) ValidateSignature() (valid bool) {
return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature) return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature)
} }
func (opts *NewTxOpts) Validate() (err error) {
func (opts *Tx) ValidateBlockId() (valid bool) { switch {
return opts.BlockId == (opts.CreatedAt/100)*100 case !opts.ValidateBlockId():
return werr.Wrapf(ErrInvalidArgument, "invalid block id")
case !opts.ValidateHash():
return werr.Wrapf(ErrInvalidArgument, "invalid hash")
case !opts.ValidateSignature():
return werr.Wrapf(ErrInvalidArgument, "invalid signature")
} }
func (opts *Tx) ValidateHash() (valid bool) { return nil
return bytes.Equal(opts.Hash, GetTxHash( }
opts.ReceiverPublicKey, opts.Message, opts.Amount, opts.BlockId, opts.CreatedAt, opts.CreatedAtNs,
func (tx *Tx) ValidateBlockId() (valid bool) {
return tx.BlockId == (tx.CreatedAt/BlockSecsDiff)*BlockSecsDiff
}
func (tx *Tx) ValidateHash() (valid bool) {
return bytes.Equal(tx.Hash, GetTxHash(
tx.ReceiverPublicKey, tx.Message, tx.Amount, tx.BlockId, tx.CreatedAt, tx.CreatedAtNs,
)) ))
} }
func (opts *Tx) ValidateSignature() (valid bool) { func (tx *Tx) ValidateSignature() (valid bool) {
return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature) return ed25519.Verify(tx.SenderPublicKey, tx.Hash, tx.Signature)
}
func (tx *Tx) Validate() (err error) {
if tx.ValidateBlockId() {
return werr.Wrapf(ErrInvalidArgument, "invalid block id")
} else if tx.ValidateHash() {
return werr.Wrapf(ErrInvalidArgument, "invalid hash")
} else if tx.ValidateSignature() {
return werr.Wrapf(ErrInvalidArgument, "invalid signature")
} else if ValidateBurningBool(tx) {
return werr.Wrapf(ErrInvalidArgument, "invalid burning")
}
return nil
} }
// HashString uses base64.RawURLEncoding encoding // HashString uses base64.RawURLEncoding encoding
@ -155,19 +174,19 @@ func (opts *NewTxOpts) SignatureString() string {
} }
// HashString uses base64.RawURLEncoding encoding // HashString uses base64.RawURLEncoding encoding
func (opts *Tx) HashString() string { func (tx *Tx) HashString() string {
return base64.RawURLEncoding.EncodeToString(opts.Hash) return base64.RawURLEncoding.EncodeToString(tx.Hash)
} }
// SignatureString uses base64.RawURLEncoding encoding // SignatureString uses base64.RawURLEncoding encoding
func (opts *Tx) SignatureString() string { func (tx *Tx) SignatureString() string {
return base64.RawURLEncoding.EncodeToString(opts.Signature) return base64.RawURLEncoding.EncodeToString(tx.Signature)
} }
func (opts *NewTxOpts) ToJSON(byteEncodingStr string) ([]byte, error) { func (opts *NewTxOpts) ToJSON(byteEncodingStr string) ([]byte, error) {
encoding, err := GetEncoding(byteEncodingStr) encoding, err := GetEncoding(byteEncodingStr)
if err != nil { if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding") return nil, werr.Wrapf(err, "error while getting byte->string encoding")
} }
data := &NewTxOptsJson{ data := &NewTxOptsJson{
Hash: encoding.EncodeToString(opts.Hash), Hash: encoding.EncodeToString(opts.Hash),
@ -184,36 +203,36 @@ func (opts *NewTxOpts) ToJSON(byteEncodingStr string) ([]byte, error) {
} }
jsonBytes, err := json.Marshal(data) jsonBytes, err := json.Marshal(data)
if err != nil { if err != nil {
return nil, werr.Wrapf(err, "error while trying to marshal to json") return nil, werr.Wrapf(err, "error while marshaling")
} }
return jsonBytes, nil return jsonBytes, nil
} }
func (opts *Tx) ToJSON(byteEncodingStr string) ([]byte, error) { func (tx *Tx) ToJSON(byteEncodingStr string) ([]byte, error) {
encoding, err := GetEncoding(byteEncodingStr) encoding, err := GetEncoding(byteEncodingStr)
if err != nil { if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding") return nil, werr.Wrapf(err, "error while getting byte->string encoding")
} }
data := &TxJson{ data := &TxJson{
Hash: encoding.EncodeToString(opts.Hash), Hash: encoding.EncodeToString(tx.Hash),
BlockId: opts.BlockId, BlockId: tx.BlockId,
SenderPublicKey: encoding.EncodeToString(opts.SenderPublicKey), SenderPublicKey: encoding.EncodeToString(tx.SenderPublicKey),
ReceiverPublicKey: encoding.EncodeToString(opts.ReceiverPublicKey), ReceiverPublicKey: encoding.EncodeToString(tx.ReceiverPublicKey),
IsReward: opts.IsReward, IsReward: tx.IsReward,
Amount: opts.Amount, Amount: tx.Amount,
AmountBurned: opts.AmountBurned, AmountBurned: tx.AmountBurned,
Message: opts.Message, Message: tx.Message,
Signature: encoding.EncodeToString(opts.Signature), Signature: encoding.EncodeToString(tx.Signature),
CreatedAt: opts.CreatedAt, CreatedAt: tx.CreatedAt,
CreatedAtNs: opts.CreatedAtNs, CreatedAtNs: tx.CreatedAtNs,
AddedAt: opts.AddedAt, AddedAt: tx.AddedAt,
IsAdded: opts.IsAdded, IsAdded: tx.IsAdded,
ByteEncoding: byteEncodingStr, ByteEncoding: byteEncodingStr,
} }
jsonBytes, err := json.Marshal(data) jsonBytes, err := json.Marshal(data)
if err != nil { if err != nil {
return nil, werr.Wrapf(err, "error while trying to marshal to json") return nil, werr.Wrapf(err, "error while marshaling")
} }
return jsonBytes, nil return jsonBytes, nil
} }
@ -229,19 +248,19 @@ func (opts *NewTxOptsJson) Process() (*NewTxOpts, error) {
} }
encoding, err := GetEncoding(opts.ByteEncoding) encoding, err := GetEncoding(opts.ByteEncoding)
if err != nil { if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding") return nil, werr.Wrapf(err, "error while getting byte->string encoding")
} }
if out.Hash, err = encoding.DecodeString(opts.Hash); err != nil { if out.Hash, err = encoding.DecodeString(opts.Hash); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode hash") return nil, werr.Wrapf(err, "error while decoding hash")
} }
if out.SenderPublicKey, err = encoding.DecodeString(opts.SenderPublicKey); err != nil { if out.SenderPublicKey, err = encoding.DecodeString(opts.SenderPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode sender public key") return nil, werr.Wrapf(err, "error while decoding sender public key")
} }
if out.ReceiverPublicKey, err = encoding.DecodeString(opts.ReceiverPublicKey); err != nil { if out.ReceiverPublicKey, err = encoding.DecodeString(opts.ReceiverPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode receiver public key") return nil, werr.Wrapf(err, "error while decoding receiver public key")
} }
if out.Signature, err = encoding.DecodeString(opts.Signature); err != nil { if out.Signature, err = encoding.DecodeString(opts.Signature); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode signature") return nil, werr.Wrapf(err, "error while decoding signature")
} }
return out, nil return out, nil
} }
@ -279,19 +298,19 @@ func (tx *TxJson) Process() (*Tx, error) {
} }
encoding, err := GetEncoding(tx.ByteEncoding) encoding, err := GetEncoding(tx.ByteEncoding)
if err != nil { if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding") return nil, err
} }
if out.Hash, err = encoding.DecodeString(tx.Hash); err != nil { if out.Hash, err = encoding.DecodeString(tx.Hash); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode hash") return nil, werr.Wrapf(err, "error while decoding hash")
} }
if out.SenderPublicKey, err = encoding.DecodeString(tx.SenderPublicKey); err != nil { if out.SenderPublicKey, err = encoding.DecodeString(tx.SenderPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode sender public key") return nil, werr.Wrapf(err, "error while decoding sender public key")
} }
if out.ReceiverPublicKey, err = encoding.DecodeString(tx.ReceiverPublicKey); err != nil { if out.ReceiverPublicKey, err = encoding.DecodeString(tx.ReceiverPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode receiver public key") return nil, werr.Wrapf(err, "error while decoding receiver public key")
} }
if out.Signature, err = encoding.DecodeString(tx.Signature); err != nil { if out.Signature, err = encoding.DecodeString(tx.Signature); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode signature") return nil, werr.Wrapf(err, "error while decoding signature")
} }
return out, nil return out, nil
} }
@ -299,14 +318,14 @@ func (tx *TxJson) Process() (*Tx, error) {
func NewTxOptsFromJSON(marshaledData []byte) (opts *NewTxOpts, err error) { func NewTxOptsFromJSON(marshaledData []byte) (opts *NewTxOpts, err error) {
data := &NewTxOptsJson{} data := &NewTxOptsJson{}
if err := json.Unmarshal(marshaledData, data); err != nil { if err := json.Unmarshal(marshaledData, data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options") return nil, werr.Wrapf(err, "error while unmarshaling")
} }
return data.Process() return data.Process()
} }
func NewTxOptsFromJsonReader(r io.Reader) (opts *NewTxOpts, err error) { func NewTxOptsFromJsonReader(r io.Reader) (opts *NewTxOpts, err error) {
data := &NewTxOptsJson{} data := &NewTxOptsJson{}
if err := json.NewDecoder(r).Decode(&data); err != nil { if err := json.NewDecoder(r).Decode(&data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options") return nil, werr.Wrapf(err, "error while unmarshaling")
} }
return data.Process() return data.Process()
} }
@ -314,14 +333,14 @@ func NewTxOptsFromJsonReader(r io.Reader) (opts *NewTxOpts, err error) {
func TxFromJSON(marshaledData []byte) (opts *Tx, err error) { func TxFromJSON(marshaledData []byte) (opts *Tx, err error) {
data := &TxJson{} data := &TxJson{}
if err := json.Unmarshal(marshaledData, data); err != nil { if err := json.Unmarshal(marshaledData, data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options") return nil, werr.Wrapf(err, "error while unmarshaling")
} }
return data.Process() return data.Process()
} }
func TxFromJsonReader(r io.Reader) (opts *Tx, err error) { func TxFromJsonReader(r io.Reader) (opts *Tx, err error) {
data := &TxJson{} data := &TxJson{}
if err := json.NewDecoder(r).Decode(&data); err != nil { if err := json.NewDecoder(r).Decode(&data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options") return nil, werr.Wrapf(err, "error while unmarshaling")
} }
return data.Process() return data.Process()
} }

8
infra.docker-compose.yml Normal file
View File

@ -0,0 +1,8 @@
services:
postgres:
image: postgres:latest
ports:
- "127.0.0.1:5432:5432"
volumes:
- ./pg_data:/var/lib/postgres
environment:

View File

@ -2,15 +2,47 @@ package http
import ( import (
"context" "context"
"encoding/json"
"mic-wallet/common"
"mic-wallet/server/core"
"net/http" "net/http"
) )
func NewApi(ctx context.Context, baseRoute string) *http.ServeMux { type ApiDependencies struct {
*core.BlocksCreator
}
/*
/ -- node info
/txs -- add/get txs
/blocks -- get/blocks
/blocks/current -- get current block info
*/
func NewApi(ctx context.Context, d ApiDependencies, baseRoute string) *http.ServeMux {
if baseRoute == "" { if baseRoute == "" {
baseRoute = "/" baseRoute = "/"
} }
mux := http.NewServeMux() mux := http.NewServeMux()
/*
*/
mux.HandleFunc(baseRoute, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
data, _ := json.Marshal(common.NodeInfo{
NodeVersion: common.VersionNumberStr,
})
w.Write(data)
w.WriteHeader(http.StatusOK)
})
return mux return mux
} }

View File

@ -3,20 +3,35 @@ package http
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"github.com/matchsystems/werr"
"log"
"mic-wallet/common"
"mic-wallet/server/core"
logicErr "mic-wallet/server/logic/err" logicErr "mic-wallet/server/logic/err"
"net/http" "net/http"
) )
type NewTransactionReqBody struct { func NewTransaction(ctx context.Context, w http.ResponseWriter, r *http.Request, d ApiDependencies) {
} defer func() {
func NewTransaction(w http.ResponseWriter, r *http.Request) *logicErr.Err {
req := &NewTransactionReqBody{}
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
}
if err := r.Body.Close(); err != nil { if err := r.Body.Close(); err != nil {
log.Printf("error closing request body: %s", err.Error())
} }
return nil }()
req := &common.NewTxOpts{}
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
if err := d.WriteTx(req); err != nil {
if errors.Is(werr.UnwrapAll(err), core.ErrTxDuplicate) {
http.Error(w, "transaction already exists", http.StatusConflict)
return
}
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
} }
func GetTransaction(w http.ResponseWriter, r *http.Request) *logicErr.Err { func GetTransaction(w http.ResponseWriter, r *http.Request) *logicErr.Err {

View File

@ -2,25 +2,33 @@ package core
import ( import (
"crypto/sha512" "crypto/sha512"
"fmt" "encoding/binary"
"github.com/matchsystems/werr"
"mic-wallet/common" "mic-wallet/common"
"mic-wallet/server/repository/entities"
"sync" "sync"
) )
type Block struct { type Block struct {
ID int64 Id int64
IgnoreSignatures map[string]struct{}
TXs map[string]*common.NewTxOpts
Lock *sync.RWMutex
PrevHash []byte PrevHash []byte
Hash []byte Hash []byte
TXs map[string]*common.NewTxOpts
// If there is some transactions added after block is finished
TXsLeftOver map[string]*common.NewTxOpts
IgnoreSignatures map[string]struct{}
Lock *sync.RWMutex
IsFinished bool
} }
func NewBlock(id int64, prevHash []byte) *Block { func NewBlock(id int64, prevHash []byte,
prevTxsLeftOver map[string]*common.NewTxOpts, txsLeftOver map[string]*common.NewTxOpts) *Block {
return &Block{ return &Block{
ID: id, Id: id,
IgnoreSignatures: make(map[string]struct{}), IgnoreSignatures: make(map[string]struct{}),
TXs: make(map[string]*common.NewTxOpts), TXs: prevTxsLeftOver,
TXsLeftOver: txsLeftOver,
Lock: &sync.RWMutex{}, Lock: &sync.RWMutex{},
PrevHash: prevHash, PrevHash: prevHash,
} }
@ -28,25 +36,41 @@ func NewBlock(id int64, prevHash []byte) *Block {
func (b *Block) AddTx(tx *common.NewTxOpts) error { func (b *Block) AddTx(tx *common.NewTxOpts) error {
b.Lock.Lock() b.Lock.Lock()
hashStr := tx.HashString()
sigStr := tx.SignatureString() sigStr := tx.SignatureString()
hashStr := tx.HashString()
if _, ok := b.IgnoreSignatures[sigStr]; ok { if _, ok := b.IgnoreSignatures[sigStr]; ok {
return fmt.Errorf("duplicate") return werr.Wrapf(common.ErrEntityExists,
"transaction with signature %s already exists", sigStr)
} }
if !b.IsFinished {
b.TXs[hashStr] = tx b.TXs[hashStr] = tx
} else {
b.TXsLeftOver[hashStr] = tx
}
b.Lock.Unlock() b.Lock.Unlock()
return nil return nil
} }
func (b *Block) RemoveTx(hash string) {} func (b *Block) Finish() ([]byte, error) {
func (b *Block) CalcHash() []byte {
hash := sha512.New() hash := sha512.New()
if err := binary.Write(hash, binary.BigEndian, b.Id); err != nil {
return nil, werr.Wrapf(err, "failed to write block id into hash buffer for block %d", b.Id)
}
b.Lock.Lock() b.Lock.Lock()
hash.Write(b.PrevHash) hash.Write(b.PrevHash)
for _, tx := range b.TXs { for _, tx := range b.TXs {
hash.Write(tx.Hash) hash.Write(tx.Hash)
} }
b.IsFinished = true
b.Lock.Unlock()
b.Hash = hash.Sum(nil) b.Hash = hash.Sum(nil)
return b.Hash return b.Hash, nil
}
func (b *Block) ToRepoEntity() *entities.Block {
return &entities.Block{
Id: b.Id,
Hash: b.Hash,
PrevHash: b.PrevHash,
}
} }

View File

@ -19,8 +19,8 @@ type IBlocksCommiter interface {
type BlocksCommiter struct { type BlocksCommiter struct {
BlocksQueue []*Block BlocksQueue []*Block
Lock *sync.RWMutex Lock *sync.RWMutex
CountLim int32 CountLim int
CountBusy int32 CountBusy int
TaskDeadline time.Duration TaskDeadline time.Duration
ErrChan chan error ErrChan chan error
@ -31,16 +31,19 @@ func NewBlocksCommiter(repo *repository.Repository, errChan chan error, workersC
return &BlocksCommiter{ return &BlocksCommiter{
BlocksQueue: make([]*Block, 0), BlocksQueue: make([]*Block, 0),
Lock: &sync.RWMutex{}, Lock: &sync.RWMutex{},
CountLim: 0, CountLim: workersCountLim,
CountBusy: 0, CountBusy: 0,
TaskDeadline: 0, TaskDeadline: 0,
ErrChan: make(chan error, workersCountLim), ErrChan: errChan,
CommitBlockFunc: func(ctx context.Context, block *Block) error { CommitBlockFunc: func(ctx context.Context, block *Block) error {
return CommitBlock(ctx, repo, block) return CommitBlock(ctx, repo, block)
}, },
} }
} }
// execute executes commiting block with deadline and cancelable by the context
// provided when calling CommitBlock.
// all the errors from it (including context) are sent to BlocksCommiter.ErrChan
func (bc *BlocksCommiter) execute(ctx context.Context, block *Block) { func (bc *BlocksCommiter) execute(ctx context.Context, block *Block) {
if bc.TaskDeadline > 0 { if bc.TaskDeadline > 0 {
var cancel context.CancelFunc var cancel context.CancelFunc
@ -55,9 +58,9 @@ func (bc *BlocksCommiter) execute(ctx context.Context, block *Block) {
}() }()
select { select {
case <-ctx.Done(): case <-ctx.Done():
errChan <- werr.Wrapf(ctx.Err(), "ctx error while commiting block %d", block.ID) bc.ErrChan <- werr.Wrapf(ctx.Err(), "ctx error while commiting block %d", block.Id)
case err := <-errChan: case err := <-errChan:
bc.ErrChan <- werr.Wrapf(err, "error while commiting block %d", block.ID) bc.ErrChan <- werr.Wrapf(err, "error while commiting block %d", block.Id)
} }
} }

View File

@ -0,0 +1,7 @@
package core
import "testing"
func TestBlocksCommiter_CommitBlock(t *testing.T) {
// TODO
}

View File

@ -8,6 +8,9 @@ import (
"time" "time"
) )
var ErrOutOfDateBlockId = errors.New("")
var ErrBlockIdDoesntExist = errors.New("")
type IBlocksCreator interface { type IBlocksCreator interface {
Run(ctx context.Context) error Run(ctx context.Context) error
WriteTx(opts *common.NewTxOpts) error WriteTx(opts *common.NewTxOpts) error
@ -16,8 +19,10 @@ type IBlocksCreator interface {
GetTxs() []*common.NewTxOpts GetTxs() []*common.NewTxOpts
} }
// BlocksCreator creates and rotates new Blocks and pushed them to the commiter
type BlocksCreator struct { type BlocksCreator struct {
blocks map[int64]*Block blocks map[int64]*Block
txsLeftOver map[string]*common.NewTxOpts
commiter IBlocksCommiter commiter IBlocksCommiter
prevBlockHash []byte prevBlockHash []byte
} }
@ -25,32 +30,58 @@ type BlocksCreator struct {
func NewBlocksCreator(commiter IBlocksCommiter) *BlocksCreator { func NewBlocksCreator(commiter IBlocksCommiter) *BlocksCreator {
return &BlocksCreator{ return &BlocksCreator{
blocks: make(map[int64]*Block), blocks: make(map[int64]*Block),
txsLeftOver: make(map[string]*common.NewTxOpts),
commiter: commiter, commiter: commiter,
} }
} }
func (bc *BlocksCreator) Run(ctx context.Context) error { func (bc *BlocksCreator) Run(ctx context.Context) (err error) {
var blockID int64 var blockID int64
var prevLeftOver map[string]*common.NewTxOpts
for { for {
blockID = common.GetBlockId(time.Now()) blockID = common.GetBlockId(time.Now())
if oldBlock, ok := bc.blocks[blockID-100]; ok { // Commit and delete old block if oldBlock, ok := bc.blocks[blockID-common.BlockSecsDiff]; ok { // Commit and delete old block
bc.prevBlockHash = oldBlock.CalcHash() // we need to know previous hash for new block.
bc.commiter.CommitBlock(ctx, oldBlock) // new transactions while oldBlock.Finish is executing will
delete(bc.blocks, blockID-100) // end up in leftOver so they will be parts of the next block
if bc.prevBlockHash, err = oldBlock.Finish(); err != nil {
return nil
} }
bc.blocks[blockID] = NewBlock(blockID, bc.prevBlockHash) go bc.commiter.CommitBlock(ctx, oldBlock) // may take a while
time.Sleep(time.Until(time.Unix(blockID+100, 0))) delete(bc.blocks, blockID-common.BlockSecsDiff)
}
// prevLeftOver becomes new block's transactions
// so there is no "lost" transactions
prevLeftOver = bc.txsLeftOver
// and also now we need to make new leftOver
bc.txsLeftOver = make(map[string]*common.NewTxOpts)
bc.blocks[blockID] = NewBlock(blockID, bc.prevBlockHash, prevLeftOver, bc.txsLeftOver)
// Waiting for the next block
time.Sleep(time.Until(time.Unix(blockID+common.BlockSecsDiff, 0)))
} }
} }
// WriteTx validates transaction and writes it into the current block
// if transaction is valid
func (bc *BlocksCreator) WriteTx(opts *common.NewTxOpts) error { func (bc *BlocksCreator) WriteTx(opts *common.NewTxOpts) error {
// validating may take some time, so to don't write into
if err := opts.Validate(); err != nil {
return werr.Wrapf(err,
"error validating transaction %s", opts.HashString())
}
currentBlockID := common.GetBlockId(time.Now()) currentBlockID := common.GetBlockId(time.Now())
if currentBlockID < opts.BlockId { if currentBlockID < opts.BlockId {
return errors.New("block with such id wasn't created yet. " + return werr.Wrapf(common.ErrEntityNotFound, "block id %d does not exist", currentBlockID)
"Ensure that you generate block IDs in UTC timezone")
} else if currentBlockID > opts.BlockId { } else if currentBlockID > opts.BlockId {
return errors.New("block id is out of date") return werr.Wrapf(common.ErrEntityOutdated, "block id %d is out of date", currentBlockID)
} }
return werr.Wrapf(bc.blocks[currentBlockID].AddTx(opts), block, ok := bc.blocks[currentBlockID]
if !ok {
return werr.Wrapf(common.ErrEntityNotFound, "block id %d does not exist", currentBlockID)
}
if err := block.AddTx(opts); err != nil {
return werr.Wrapf(err,
"error adding transaction to block %d", currentBlockID) "error adding transaction to block %d", currentBlockID)
} }
return nil
}

View File

@ -2,9 +2,46 @@ package core
import ( import (
"context" "context"
"github.com/matchsystems/werr"
"mic-wallet/common"
"mic-wallet/server/repository" "mic-wallet/server/repository"
"mic-wallet/server/repository/entities"
) )
func CommitBlock(ctx context.Context, repo *repository.Repository, block *Block) error { func NewTxOptsToRepoEntity(opts *common.NewTxOpts) *entities.NewTransactionOpts {
return nil return &entities.NewTransactionOpts{
Hash: opts.Hash,
BlockId: opts.BlockId,
SenderPublicKey: opts.SenderPublicKey,
ReceiverPublicKey: opts.ReceiverPublicKey,
IsReward: opts.IsReward,
Amount: opts.Amount,
AmountBurned: common.CalcBurning(opts.Amount),
Message: opts.Message,
Signature: opts.Signature,
CreatedAt: opts.CreatedAt,
CreatedAtNs: opts.CreatedAtNs,
}
}
func CommitBlock(ctx context.Context, repo *repository.Repository, block *Block) (err error) {
// If micw will be decentralized
// this function will be sending blocks to other nodes
// and then there will be block-fetcher to see which blocks were accepted to the network
if err = repo.AddBLock(ctx, &entities.Block{
Id: block.Id,
Hash: block.Hash,
PrevHash: block.PrevHash,
}); err != nil {
return werr.Wrapf(err, "failed to commit block")
}
repoTxOpts := make([]*entities.NewTransactionOpts, len(block.TXs))
var i int
for _, tx := range block.TXs {
repoTxOpts[i] = NewTxOptsToRepoEntity(tx)
i++
}
return werr.Wrapf(repo.AddTransactions(ctx, repoTxOpts), "failed to commit transactions")
} }

View File

@ -11,7 +11,6 @@ CREATE TABLE blocks (
hash BYTEA UNIQUE, hash BYTEA UNIQUE,
-- NULL only on the first block -- NULL only on the first block
prev_hash BYTEA UNIQUE, prev_hash BYTEA UNIQUE,
finished_at TIMESTAMP,
) )
CREATE TABLE IF NOT EXISTS transactions ( CREATE TABLE IF NOT EXISTS transactions (

View File

@ -9,8 +9,7 @@ type (
Id int64 `db:"id"` Id int64 `db:"id"`
Hash []byte `db:"hash"` Hash []byte `db:"hash"`
PrevHash []byte `db:"prev_hash"` PrevHash []byte `db:"prev_hash"`
StartedAt time.Time `db:"started_at"` FinishedAt time.Time `db:"finished_at"`
FinishedAt *time.Time `db:"finished_at"`
Transactions []Transaction `db:"transactions"` Transactions []Transaction `db:"transactions"`
} }
@ -18,23 +17,8 @@ type (
Id int64 `db:"id"` Id int64 `db:"id"`
Hash []byte `db:"hash"` Hash []byte `db:"hash"`
PrevHash []byte `db:"prev_hash"` PrevHash []byte `db:"prev_hash"`
StartedAt time.Time `db:"started_at"`
FinishedAt *time.Time `db:"finished_at"` FinishedAt *time.Time `db:"finished_at"`
} }
NewBlockOpts struct {
Id int64 `db:"id"`
Hash []byte `db:"hash"` // May be nil
PrevHash []byte `db:"prev_hash"` // May be nil
StartedAt time.Time `db:"started_at"`
FinishedAt *time.Time `db:"finished_at"` // May be nil
}
BlockFinishedOpts struct {
Id int64 `db:"id"`
Hash []byte `db:"hash"`
FinishedAt time.Time `db:"finished_at"`
}
) )
type ( type (
@ -63,7 +47,7 @@ type (
AmountBurned float64 `db:"amount_burned"` AmountBurned float64 `db:"amount_burned"`
Message string `db:"message"` Message string `db:"message"`
Signature []byte `db:"signature"` Signature []byte `db:"signature"`
CreatedAt time.Time `db:"created_at"` CreatedAt int64 `db:"created_at"`
CreatedAtNs int64 `db:"created_at_ns"` CreatedAtNs int64 `db:"created_at_ns"`
} }
) )

View File

@ -7,25 +7,28 @@ import (
) )
type IBlocksRepository interface { type IBlocksRepository interface {
NewBLock(ctx context.Context, opts e.NewBlockOpts) (blockID int64, err error) AddBLock(ctx context.Context, opts *e.Block) (err error)
FinishBlock(ctx context.Context, opts e.BlockFinishedOpts) (err error)
GetBlock(ctx context.Context, id int64) (block *e.Block, err error) GetBlock(ctx context.Context, id int64) (block *e.Block, err error)
GetBlockByHash(ctx context.Context, hash string) (block *e.Block, err error) GetBlockByHash(ctx context.Context, hash string) (block *e.Block, err error)
GetLastFinishedBlock(ctx context.Context) (block *e.Block, err error)
GetBlockWithTxs(ctx context.Context, id int64) (block *e.BlockWithTransactions, err error)
GetBlockWithTxsByHash(ctx context.Context, hash string) (block *e.BlockWithTransactions, err error)
} }
type ITransactionsRepository interface { type ITransactionsRepository interface {
GetLastTransactionHash(ctx context.Context) ([]byte, error) AddTransaction(ctx context.Context, opts *e.NewTransactionOpts) (transaction *e.Transaction, err error)
AddTransaction(ctx context.Context, opts e.NewTransactionOpts) (transaction *e.Transaction, err error) AddTransactions(ctx context.Context, opts []*e.NewTransactionOpts) (err error)
GetTransaction(ctx context.Context, txID int64) (tx e.Transaction, err error)
GetTransaction(ctx context.Context, hash string) (tx *e.Transaction, err error)
GetUserIncomingTransactions( GetUserIncomingTransactions(
ctx context.Context, userPubKey string, orderAsc bool, limit int, offset int) ([]e.Transaction, error) ctx context.Context, userPubKey string, orderAsc bool, limit int, offset int) ([]*e.Transaction, error)
GetUserOutcomingTransactions( GetUserOutcomingTransactions(
ctx context.Context, userPubKey string, orderAsc bool, limit int, offset int) ([]e.Transaction, error) ctx context.Context, userPubKey string, orderAsc bool, limit int, offset int) ([]*e.Transaction, error)
GetUserTransactions( GetUserTransactions(
ctx context.Context, userPubKey string, orderAsc bool, limit int, offset int) ([]e.Transaction, error) ctx context.Context, userPubKey string, orderAsc bool, limit int, offset int) ([]*e.Transaction, error)
GetTransactions( GetTransactions(
ctx context.Context, orderAsc bool, limit int, offset int) ([]e.Transaction, error) ctx context.Context, orderAsc bool, limit int, offset int) ([]*e.Transaction, error)
} }
type Repository struct { type Repository struct {

View File

@ -3,34 +3,36 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
commonUtils "git.mic.pp.ua/anderson/nettools/common"
"net/http" "net/http"
) )
func (s *Server) Run(ctx context.Context) error { func (s *Server) Run(ctx context.Context) error {
tasks := make([]commonUtils.Task, 0) s.ErrChan = make(chan error)
defer close(s.ErrChan)
tasks := make([]serverTask, 0)
var mux *http.ServeMux var mux *http.ServeMux
if s.HttpListener != nil || s.HttpsListener != nil { if s.HttpListener != nil || s.HttpsListener != nil {
mux = http.NewServeMux() mux = http.NewServeMux()
} }
if s.HttpListener != nil { if s.HttpListener != nil {
tasks = append(tasks, func(ctx context.Context) error { tasks = append(tasks, func() error {
return http.Serve(s.HttpListener, mux) return s.RunHttp(mux)
}) })
} }
if s.HttpsListener != nil { if s.HttpsListener != nil {
tasks = append(tasks, func(ctx context.Context) error { tasks = append(tasks, func() error {
return http.ServeTLS(s.HttpsListener, mux, s.Cfg.TlsCfg.Cert, s.Cfg.TlsCfg.Key) return s.RunHttps(mux)
}) })
} }
if s.GrpcListener != nil { if s.GrpcListener != nil {
tasks = append(tasks, func(ctx context.Context) error { tasks = append(tasks, func() error {
return nil return s.RunGrpc()
}) })
} }
return commonUtils.ExecTasks(ctx, 0, tasks) return s.execTasks(ctx, tasks)
} }
func (s *Server) RunHttp(mux http.Handler) error { func (s *Server) RunHttp(mux http.Handler) error {

View File

@ -2,38 +2,45 @@ package server
import ( import (
"context" "context"
commonUtils "git.mic.pp.ua/anderson/nettools/common"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"mic-wallet/server/config" "mic-wallet/server/config"
"mic-wallet/server/core"
"mic-wallet/server/repository" "mic-wallet/server/repository"
"net" "net"
"net/http" "net/http"
) )
type serverTask func() error
type Server struct { type Server struct {
Cfg *config.Processed Cfg *config.Processed
DB *pgxpool.Pool DB *pgxpool.Pool
Repo *repository.Repository
BlocksCommiter *core.BlocksCommiter
BlocksCreator *core.BlocksCreator
HttpListener net.Listener HttpListener net.Listener
HttpsListener net.Listener HttpsListener net.Listener
GrpcListener net.Listener GrpcListener net.Listener
Mux *http.ServeMux Mux *http.ServeMux
Repo *repository.Repository ErrChan chan error
} }
func New(cfg *config.Processed) *Server { func New(cfg *config.Processed) *Server {
return &Server{Cfg: cfg} return &Server{
Cfg: cfg,
ErrChan: make(chan error),
}
} }
func (s *Server) execTasks(ctx context.Context, tasks []commonUtils.Task) error { func (s *Server) execTasks(ctx context.Context, tasks []serverTask) error {
errChan := make(chan error)
for _, task := range tasks { for _, task := range tasks {
go func() { go func() {
errChan <- task(ctx) s.ErrChan <- task()
}() }()
} }
for { for {
select { select {
case err := <-errChan: case err := <-s.ErrChan:
if err != nil { if err != nil {
return err return err
} }

View File

@ -11,16 +11,15 @@ import (
_ "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
"github.com/matchsystems/werr" "github.com/matchsystems/werr"
"log" "log"
"mic-wallet/server/core"
"mic-wallet/server/repository" "mic-wallet/server/repository"
"net" "net"
"net/http" "net/http"
"runtime"
"time" "time"
) )
func (s *Server) Setup(ctx context.Context) error { func (s *Server) Setup(ctx context.Context) error {
// ================================
// Set up the transactions pipeline
// =================== // ===================
// Setting up database // Setting up database
@ -67,7 +66,13 @@ func (s *Server) Setup(ctx context.Context) error {
// Internals initialization // Internals initialization
s.Repo = repository.NewRepository(s.DB) s.Repo = repository.NewRepository(s.DB)
// TODO:
commiterWorkersLim := runtime.NumCPU() - 1
if commiterWorkersLim < 1 {
commiterWorkersLim = 1
}
s.BlocksCommiter = core.NewBlocksCommiter(s.Repo, s.ErrChan, commiterWorkersLim)
s.BlocksCreator = core.NewBlocksCreator(s.BlocksCommiter)
// ====================== // ======================
// Creating net listeners // Creating net listeners
@ -79,7 +84,7 @@ func (s *Server) Setup(ctx context.Context) error {
addr := fmt.Sprintf("%s:%d", s.Cfg.Addr, s.Cfg.HttpPort) addr := fmt.Sprintf("%s:%d", s.Cfg.Addr, s.Cfg.HttpPort)
s.HttpListener, err = net.Listen("tcp", addr) s.HttpListener, err = net.Listen("tcp", addr)
if err != nil { if err != nil {
return werr.Wrapf(err, "failed to listen http_api_entities on address %s", addr) return werr.Wrapf(err, "failed to listen http on address %s", addr)
} }
} }
if s.Cfg.HttpsPort > 0 { if s.Cfg.HttpsPort > 0 {