Minor changes
This commit is contained in:
parent
4e044cdad9
commit
3c069df5c1
8
.idea/.gitignore
vendored
8
.idea/.gitignore
vendored
@ -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
|
|
@ -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
50
common/base_errors.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
128
common/block.go
128
common/block.go
@ -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
3
common/block_test.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
// TODO
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
34
common/get_txs.go
Normal 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
5
common/node_info.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
type NodeInfo struct {
|
||||||
|
NodeVersion string `json:"node_version"`
|
||||||
|
}
|
119
common/tx.go
119
common/tx.go
@ -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
8
infra.docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:5432:5432"
|
||||||
|
volumes:
|
||||||
|
- ./pg_data:/var/lib/postgres
|
||||||
|
environment:
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
server/core/blocks_commiter_test.go
Normal file
7
server/core/blocks_commiter_test.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestBlocksCommiter_CommitBlock(t *testing.T) {
|
||||||
|
// TODO
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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 (
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user