common and core changed
This commit is contained in:
parent
6dfb61d447
commit
4e044cdad9
10
common/block.go
Normal file
10
common/block.go
Normal file
@ -0,0 +1,10 @@
|
||||
package common
|
||||
|
||||
type BlockJson struct {
|
||||
}
|
||||
|
||||
type Block struct {
|
||||
Id int64
|
||||
PrevHash []byte
|
||||
Hash []byte
|
||||
}
|
9
common/burning.go
Normal file
9
common/burning.go
Normal file
@ -0,0 +1,9 @@
|
||||
package common
|
||||
|
||||
import "math"
|
||||
|
||||
const BurningRate = 0.001
|
||||
|
||||
func CalcBurning(trxAmount float64) float64 {
|
||||
return BurningRate * math.Sqrt(trxAmount)
|
||||
}
|
31
common/common.go
Normal file
31
common/common.go
Normal file
@ -0,0 +1,31 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
EncodingRawUrl = "encoring_row_url"
|
||||
EncodingRawStd = "encoring_row_std"
|
||||
)
|
||||
|
||||
func GetEncoding(encoding string) (*base64.Encoding, error) {
|
||||
switch encoding {
|
||||
case EncodingRawUrl:
|
||||
return base64.RawURLEncoding, nil
|
||||
case EncodingRawStd:
|
||||
return base64.RawStdEncoding, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported encoding")
|
||||
}
|
||||
}
|
||||
|
||||
// float64ToBytes converts float64 to []byte
|
||||
// May be moved to nettools later
|
||||
func float64ToBytes(f float64) []byte {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(f))
|
||||
return buf
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package common
|
||||
|
||||
const VersionNumber = 0.01
|
||||
const MinBurningAmount = 0.001
|
||||
|
||||
func CalcBurning(trxAmount float64) float64 {
|
||||
if trxAmount < 1 {
|
||||
return MinBurningAmount
|
||||
}
|
||||
return trxAmount * MinBurningAmount
|
||||
}
|
4
common/http_err.go
Normal file
4
common/http_err.go
Normal file
@ -0,0 +1,4 @@
|
||||
package common
|
||||
|
||||
type HttpErr struct {
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
ed "crypto/ed25519"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Float64ToBytes(f float64) []byte {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(f))
|
||||
return buf
|
||||
}
|
||||
|
||||
func GetTransactionHash(receiverPubKey []byte, message string, amount float64, createdAt time.Time) []byte {
|
||||
amountBytes := Float64ToBytes(amount)
|
||||
hash := sha512.New()
|
||||
timestamp := createdAt.Unix()
|
||||
_ = binary.Write(hash, binary.BigEndian, timestamp)
|
||||
hash.Write([]byte(message))
|
||||
hash.Write(receiverPubKey)
|
||||
hash.Write(amountBytes)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
func getSignMsg(txHash []byte, createdAt time.Time) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(txHash)
|
||||
timestamp := createdAt.Unix()
|
||||
if err := binary.Write(buf, binary.BigEndian, timestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func SingTransaction(key ed.PrivateKey, txHash []byte, signedAt time.Time) (sig []byte, err error) {
|
||||
msg, err := getSignMsg(txHash, signedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig = ed.Sign(key, msg)
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func VerifyTransactionSignature(key ed.PublicKey, signature []byte, txHash []byte, signedAt time.Time) (valid bool, err error) {
|
||||
msg, err := getSignMsg(txHash, signedAt)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return ed.Verify(key, msg, signature), nil
|
||||
}
|
327
common/tx.go
Normal file
327
common/tx.go
Normal file
@ -0,0 +1,327 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"github.com/matchsystems/werr"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetTxHash(receiverPubKey []byte, message string, amount float64, blockId, timestamp, timestampNs int64) []byte {
|
||||
hash := sha512.New()
|
||||
_ = binary.Write(hash, binary.BigEndian, blockId)
|
||||
_ = binary.Write(hash, binary.BigEndian, timestamp)
|
||||
_ = binary.Write(hash, binary.BigEndian, timestampNs)
|
||||
hash.Write(float64ToBytes(amount))
|
||||
hash.Write([]byte(message))
|
||||
hash.Write(receiverPubKey)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
func GetBlockId(t time.Time) int64 {
|
||||
return (t.UTC().Unix() / 100) * 100
|
||||
}
|
||||
|
||||
type NewTxOptsJson struct {
|
||||
Hash string `json:"hash"` // use function GetTxHash
|
||||
// (CreatedAt / 100) * 100
|
||||
BlockId int64 `json:"block_id"`
|
||||
SenderPublicKey string `json:"sender_public_key"`
|
||||
ReceiverPublicKey string `json:"receiver_public_key"`
|
||||
IsReward bool `json:"is_reward"`
|
||||
Amount float64 `json:"amount"`
|
||||
Message string `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
CreatedAtNs int64 `json:"created_at_ns"`
|
||||
|
||||
ByteEncoding string `json:"byte_encoding"`
|
||||
}
|
||||
|
||||
// NewTxOpts sent from client to server to add a new transaction
|
||||
type NewTxOpts struct {
|
||||
Hash []byte
|
||||
BlockId int64
|
||||
SenderPublicKey []byte
|
||||
ReceiverPublicKey []byte
|
||||
IsReward bool
|
||||
Amount float64
|
||||
Message string
|
||||
// Signed TX Hash with senders private key
|
||||
Signature []byte
|
||||
CreatedAt int64
|
||||
CreatedAtNs int64 // Nanoseconds
|
||||
}
|
||||
|
||||
type TxJson struct {
|
||||
Hash string `json:"hash"` // use function GetTxHash
|
||||
// (CreatedAt / 100) * 100
|
||||
BlockId int64 `json:"block_id"`
|
||||
SenderPublicKey string `json:"sender_public_key"`
|
||||
ReceiverPublicKey string `json:"receiver_public_key"`
|
||||
IsReward bool `json:"is_reward"`
|
||||
Amount float64 `json:"amount"`
|
||||
AmountBurned float64 `json:"amount_burned"`
|
||||
Message string `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
CreatedAtNs int64 `json:"created_at_ns"`
|
||||
AddedAt time.Time `json:"added_at"`
|
||||
IsAdded bool `json:"is_added"`
|
||||
|
||||
ByteEncoding string `json:"byte_encoding"`
|
||||
}
|
||||
|
||||
// Tx is a transaction structure
|
||||
// that is sent from server to the client
|
||||
// with the information about requested transaction
|
||||
type Tx struct {
|
||||
Hash []byte
|
||||
BlockId int64
|
||||
SenderPublicKey []byte
|
||||
ReceiverPublicKey []byte
|
||||
IsReward bool
|
||||
Amount float64
|
||||
AmountBurned float64
|
||||
Message string
|
||||
// Signed TX Hash with senders private key
|
||||
Signature []byte
|
||||
CreatedAt int64
|
||||
CreatedAtNs int64 // Nanoseconds
|
||||
AddedAt time.Time
|
||||
IsAdded bool
|
||||
}
|
||||
|
||||
func MakeNewTxOpts(senderPubKey, senderPrivateKey, receiverPubKey []byte,
|
||||
amount float64, msg string, isReward bool) *NewTxOpts {
|
||||
|
||||
now := time.Now().UTC()
|
||||
createdAt := now.Unix()
|
||||
createdAtNs := now.UnixNano()
|
||||
blockId := (createdAt / 100) * 100
|
||||
txHash := GetTxHash(receiverPubKey, msg, amount, blockId, createdAt, createdAtNs)
|
||||
sig := ed25519.Sign(senderPrivateKey, txHash)
|
||||
return &NewTxOpts{
|
||||
Hash: txHash,
|
||||
BlockId: blockId,
|
||||
SenderPublicKey: senderPubKey,
|
||||
ReceiverPublicKey: receiverPubKey,
|
||||
IsReward: isReward,
|
||||
Amount: amount,
|
||||
Message: msg,
|
||||
Signature: sig,
|
||||
CreatedAt: createdAt,
|
||||
CreatedAtNs: createdAtNs,
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *NewTxOpts) ValidateBlockId() (valid bool) {
|
||||
return opts.BlockId == (opts.CreatedAt/100)*100
|
||||
}
|
||||
func (opts *NewTxOpts) ValidateHash() (valid bool) {
|
||||
return bytes.Equal(opts.Hash, GetTxHash(
|
||||
opts.ReceiverPublicKey, opts.Message, opts.Amount, opts.BlockId, opts.CreatedAt, opts.CreatedAtNs,
|
||||
))
|
||||
}
|
||||
func (opts *NewTxOpts) ValidateSignature() (valid bool) {
|
||||
return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature)
|
||||
}
|
||||
|
||||
func (opts *Tx) ValidateBlockId() (valid bool) {
|
||||
return opts.BlockId == (opts.CreatedAt/100)*100
|
||||
}
|
||||
func (opts *Tx) ValidateHash() (valid bool) {
|
||||
return bytes.Equal(opts.Hash, GetTxHash(
|
||||
opts.ReceiverPublicKey, opts.Message, opts.Amount, opts.BlockId, opts.CreatedAt, opts.CreatedAtNs,
|
||||
))
|
||||
}
|
||||
func (opts *Tx) ValidateSignature() (valid bool) {
|
||||
return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature)
|
||||
}
|
||||
|
||||
// HashString uses base64.RawURLEncoding encoding
|
||||
func (opts *NewTxOpts) HashString() string {
|
||||
return base64.RawURLEncoding.EncodeToString(opts.Hash)
|
||||
}
|
||||
|
||||
// SignatureString uses base64.RawURLEncoding encoding
|
||||
func (opts *NewTxOpts) SignatureString() string {
|
||||
return base64.RawURLEncoding.EncodeToString(opts.Signature)
|
||||
}
|
||||
|
||||
// HashString uses base64.RawURLEncoding encoding
|
||||
func (opts *Tx) HashString() string {
|
||||
return base64.RawURLEncoding.EncodeToString(opts.Hash)
|
||||
}
|
||||
|
||||
// SignatureString uses base64.RawURLEncoding encoding
|
||||
func (opts *Tx) SignatureString() string {
|
||||
return base64.RawURLEncoding.EncodeToString(opts.Signature)
|
||||
}
|
||||
|
||||
func (opts *NewTxOpts) ToJSON(byteEncodingStr string) ([]byte, error) {
|
||||
encoding, err := GetEncoding(byteEncodingStr)
|
||||
if err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
|
||||
}
|
||||
data := &NewTxOptsJson{
|
||||
Hash: encoding.EncodeToString(opts.Hash),
|
||||
BlockId: opts.BlockId,
|
||||
SenderPublicKey: encoding.EncodeToString(opts.SenderPublicKey),
|
||||
ReceiverPublicKey: encoding.EncodeToString(opts.ReceiverPublicKey),
|
||||
IsReward: opts.IsReward,
|
||||
Amount: opts.Amount,
|
||||
Message: opts.Message,
|
||||
Signature: encoding.EncodeToString(opts.Signature),
|
||||
CreatedAt: opts.CreatedAt,
|
||||
CreatedAtNs: opts.CreatedAtNs,
|
||||
ByteEncoding: byteEncodingStr,
|
||||
}
|
||||
jsonBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to marshal to json")
|
||||
}
|
||||
return jsonBytes, nil
|
||||
}
|
||||
|
||||
func (opts *Tx) ToJSON(byteEncodingStr string) ([]byte, error) {
|
||||
encoding, err := GetEncoding(byteEncodingStr)
|
||||
if err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
|
||||
}
|
||||
data := &TxJson{
|
||||
Hash: encoding.EncodeToString(opts.Hash),
|
||||
BlockId: opts.BlockId,
|
||||
SenderPublicKey: encoding.EncodeToString(opts.SenderPublicKey),
|
||||
ReceiverPublicKey: encoding.EncodeToString(opts.ReceiverPublicKey),
|
||||
IsReward: opts.IsReward,
|
||||
Amount: opts.Amount,
|
||||
AmountBurned: opts.AmountBurned,
|
||||
Message: opts.Message,
|
||||
Signature: encoding.EncodeToString(opts.Signature),
|
||||
CreatedAt: opts.CreatedAt,
|
||||
CreatedAtNs: opts.CreatedAtNs,
|
||||
AddedAt: opts.AddedAt,
|
||||
IsAdded: opts.IsAdded,
|
||||
|
||||
ByteEncoding: byteEncodingStr,
|
||||
}
|
||||
jsonBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to marshal to json")
|
||||
}
|
||||
return jsonBytes, nil
|
||||
}
|
||||
|
||||
func (opts *NewTxOptsJson) Process() (*NewTxOpts, error) {
|
||||
out := &NewTxOpts{
|
||||
BlockId: opts.BlockId,
|
||||
IsReward: opts.IsReward,
|
||||
Amount: opts.Amount,
|
||||
Message: opts.Message,
|
||||
CreatedAt: opts.CreatedAt,
|
||||
CreatedAtNs: opts.CreatedAtNs,
|
||||
}
|
||||
encoding, err := GetEncoding(opts.ByteEncoding)
|
||||
if err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
|
||||
}
|
||||
if out.Hash, err = encoding.DecodeString(opts.Hash); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode hash")
|
||||
}
|
||||
if out.SenderPublicKey, err = encoding.DecodeString(opts.SenderPublicKey); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode sender public key")
|
||||
}
|
||||
if out.ReceiverPublicKey, err = encoding.DecodeString(opts.ReceiverPublicKey); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode receiver public key")
|
||||
}
|
||||
if out.Signature, err = encoding.DecodeString(opts.Signature); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode signature")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (opts *NewTxOpts) Process(isAdded bool, addedAt time.Time) (*Tx, error) {
|
||||
out := &Tx{
|
||||
Hash: opts.Hash,
|
||||
BlockId: opts.BlockId,
|
||||
SenderPublicKey: opts.SenderPublicKey,
|
||||
ReceiverPublicKey: opts.ReceiverPublicKey,
|
||||
IsReward: opts.IsReward,
|
||||
Amount: opts.Amount,
|
||||
AmountBurned: CalcBurning(opts.Amount),
|
||||
Message: opts.Message,
|
||||
Signature: opts.Signature,
|
||||
CreatedAt: opts.CreatedAt,
|
||||
CreatedAtNs: opts.CreatedAtNs,
|
||||
AddedAt: addedAt,
|
||||
IsAdded: isAdded,
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (tx *TxJson) Process() (*Tx, error) {
|
||||
out := &Tx{
|
||||
BlockId: tx.BlockId,
|
||||
IsReward: tx.IsReward,
|
||||
Amount: tx.Amount,
|
||||
AmountBurned: tx.AmountBurned,
|
||||
Message: tx.Message,
|
||||
CreatedAt: tx.CreatedAt,
|
||||
CreatedAtNs: tx.CreatedAtNs,
|
||||
AddedAt: tx.AddedAt,
|
||||
IsAdded: tx.IsAdded,
|
||||
}
|
||||
encoding, err := GetEncoding(tx.ByteEncoding)
|
||||
if err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
|
||||
}
|
||||
if out.Hash, err = encoding.DecodeString(tx.Hash); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode hash")
|
||||
}
|
||||
if out.SenderPublicKey, err = encoding.DecodeString(tx.SenderPublicKey); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode sender public key")
|
||||
}
|
||||
if out.ReceiverPublicKey, err = encoding.DecodeString(tx.ReceiverPublicKey); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode receiver public key")
|
||||
}
|
||||
if out.Signature, err = encoding.DecodeString(tx.Signature); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to decode signature")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func NewTxOptsFromJSON(marshaledData []byte) (opts *NewTxOpts, err error) {
|
||||
data := &NewTxOptsJson{}
|
||||
if err := json.Unmarshal(marshaledData, data); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
|
||||
}
|
||||
return data.Process()
|
||||
}
|
||||
func NewTxOptsFromJsonReader(r io.Reader) (opts *NewTxOpts, err error) {
|
||||
data := &NewTxOptsJson{}
|
||||
if err := json.NewDecoder(r).Decode(&data); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
|
||||
}
|
||||
return data.Process()
|
||||
}
|
||||
|
||||
func TxFromJSON(marshaledData []byte) (opts *Tx, err error) {
|
||||
data := &TxJson{}
|
||||
if err := json.Unmarshal(marshaledData, data); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
|
||||
}
|
||||
return data.Process()
|
||||
}
|
||||
func TxFromJsonReader(r io.Reader) (opts *Tx, err error) {
|
||||
data := &TxJson{}
|
||||
if err := json.NewDecoder(r).Decode(&data); err != nil {
|
||||
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
|
||||
}
|
||||
return data.Process()
|
||||
}
|
74
common/tx_test.go
Normal file
74
common/tx_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTx(t *testing.T) {
|
||||
senderPublicKey, senderPrivateKey, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %s", err.Error())
|
||||
}
|
||||
_, receiverPublicKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %s", err.Error())
|
||||
}
|
||||
newTxOpts := MakeNewTxOpts(
|
||||
senderPublicKey, senderPrivateKey, receiverPublicKey,
|
||||
0.01, "", false)
|
||||
|
||||
newTxOptsJson, err := newTxOpts.ToJSON(EncodingRawUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal JSON to tx: %s", err.Error())
|
||||
}
|
||||
newTxOptsFromJSON, err := NewTxOptsFromJSON(newTxOptsJson)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal tx from JSON: %s", err.Error())
|
||||
}
|
||||
if ok := newTxOptsFromJSON.ValidateBlockId(); !ok {
|
||||
t.Fatalf("invalid block id")
|
||||
}
|
||||
if ok := newTxOptsFromJSON.ValidateHash(); !ok {
|
||||
t.Fatalf("invalid hash. Got hash: %s", base64.RawURLEncoding.EncodeToString(newTxOptsFromJSON.Hash))
|
||||
}
|
||||
if ok := newTxOptsFromJSON.ValidateSignature(); !ok {
|
||||
t.Fatalf("invalid signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTx(t *testing.T) {
|
||||
senderPublicKey, senderPrivateKey, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %s", err.Error())
|
||||
}
|
||||
_, receiverPublicKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %s", err.Error())
|
||||
}
|
||||
newTxOpts := MakeNewTxOpts(
|
||||
senderPublicKey, senderPrivateKey, receiverPublicKey,
|
||||
0.01, "", false)
|
||||
tx, _ := newTxOpts.Process(false, time.Time{})
|
||||
|
||||
txJson, err := tx.ToJSON(EncodingRawUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal JSON to tx: %s", err.Error())
|
||||
}
|
||||
txFromJson, err := TxFromJSON(txJson)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal tx from JSON: %s", err.Error())
|
||||
}
|
||||
if ok := txFromJson.ValidateBlockId(); !ok {
|
||||
t.Fatalf("invalid block id")
|
||||
}
|
||||
if ok := txFromJson.ValidateHash(); !ok {
|
||||
t.Fatalf("invalid hash. Got hash: %s", base64.RawURLEncoding.EncodeToString(txFromJson.Hash))
|
||||
}
|
||||
if ok := txFromJson.ValidateSignature(); !ok {
|
||||
t.Fatalf("invalid signature")
|
||||
}
|
||||
}
|
7
common/version.go
Normal file
7
common/version.go
Normal file
@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
import "fmt"
|
||||
|
||||
var VersionNumber = []int{0, 1, 0}
|
||||
var VersionNumberStr = fmt.Sprintf("v%d.%d.%d",
|
||||
VersionNumber[0], VersionNumber[1], VersionNumber[2])
|
52
server/core/block.go
Normal file
52
server/core/block.go
Normal file
@ -0,0 +1,52 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"mic-wallet/common"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
ID int64
|
||||
IgnoreSignatures map[string]struct{}
|
||||
TXs map[string]*common.NewTxOpts
|
||||
Lock *sync.RWMutex
|
||||
PrevHash []byte
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
func NewBlock(id int64, prevHash []byte) *Block {
|
||||
return &Block{
|
||||
ID: id,
|
||||
IgnoreSignatures: make(map[string]struct{}),
|
||||
TXs: make(map[string]*common.NewTxOpts),
|
||||
Lock: &sync.RWMutex{},
|
||||
PrevHash: prevHash,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) AddTx(tx *common.NewTxOpts) error {
|
||||
b.Lock.Lock()
|
||||
hashStr := tx.HashString()
|
||||
sigStr := tx.SignatureString()
|
||||
if _, ok := b.IgnoreSignatures[sigStr]; ok {
|
||||
return fmt.Errorf("duplicate")
|
||||
}
|
||||
b.TXs[hashStr] = tx
|
||||
b.Lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Block) RemoveTx(hash string) {}
|
||||
|
||||
func (b *Block) CalcHash() []byte {
|
||||
hash := sha512.New()
|
||||
b.Lock.Lock()
|
||||
hash.Write(b.PrevHash)
|
||||
for _, tx := range b.TXs {
|
||||
hash.Write(tx.Hash)
|
||||
}
|
||||
b.Hash = hash.Sum(nil)
|
||||
return b.Hash
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mic-wallet/server/repository/entities"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BlockData struct {
|
||||
Lock *sync.RWMutex
|
||||
prevHash []byte
|
||||
Transactions map[string]entities.NewTransactionOpts
|
||||
}
|
||||
|
||||
// BlocksPipeline does block rotation, writes transaction into the block or block queue
|
||||
// write blocks when they are finished.
|
||||
type BlocksPipeline struct {
|
||||
cooldown time.Duration
|
||||
writeToBlock uint8
|
||||
block1 *BlockData
|
||||
block2 *BlockData
|
||||
block3 *BlockData
|
||||
}
|
||||
|
||||
func NewBlockPipeline(cooldown time.Duration) *BlocksPipeline {
|
||||
return &BlocksPipeline{}
|
||||
}
|
||||
|
||||
func (b *BlocksPipeline) loadPrevBlockHash(ctx context.Context) {
|
||||
|
||||
}
|
||||
|
||||
func (b *BlocksPipeline) rotate(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BlocksPipeline) NewTransaction(ctx context.Context, opts NewTransactionOpts) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BlocksPipeline) Run(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
84
server/core/blocks_commiter.go
Normal file
84
server/core/blocks_commiter.go
Normal file
@ -0,0 +1,84 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/matchsystems/werr"
|
||||
"mic-wallet/server/repository"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommitBlockFunc func(ctx context.Context, block *Block) error
|
||||
|
||||
type IBlocksCommiter interface {
|
||||
// CommitBlock pushes blocks into the storage (db).
|
||||
// Block must be not null
|
||||
CommitBlock(ctx context.Context, block *Block)
|
||||
}
|
||||
|
||||
type BlocksCommiter struct {
|
||||
BlocksQueue []*Block
|
||||
Lock *sync.RWMutex
|
||||
CountLim int32
|
||||
CountBusy int32
|
||||
TaskDeadline time.Duration
|
||||
ErrChan chan error
|
||||
|
||||
CommitBlockFunc CommitBlockFunc
|
||||
}
|
||||
|
||||
func NewBlocksCommiter(repo *repository.Repository, errChan chan error, workersCountLim int) *BlocksCommiter {
|
||||
return &BlocksCommiter{
|
||||
BlocksQueue: make([]*Block, 0),
|
||||
Lock: &sync.RWMutex{},
|
||||
CountLim: 0,
|
||||
CountBusy: 0,
|
||||
TaskDeadline: 0,
|
||||
ErrChan: make(chan error, workersCountLim),
|
||||
CommitBlockFunc: func(ctx context.Context, block *Block) error {
|
||||
return CommitBlock(ctx, repo, block)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (bc *BlocksCommiter) execute(ctx context.Context, block *Block) {
|
||||
if bc.TaskDeadline > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, bc.TaskDeadline)
|
||||
defer cancel()
|
||||
}
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
if err := bc.CommitBlockFunc(ctx, block); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errChan <- werr.Wrapf(ctx.Err(), "ctx error while commiting block %d", block.ID)
|
||||
case err := <-errChan:
|
||||
bc.ErrChan <- werr.Wrapf(err, "error while commiting block %d", block.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (bc *BlocksCommiter) CommitBlock(ctx context.Context, block *Block) {
|
||||
bc.Lock.Lock()
|
||||
if bc.CountBusy >= bc.CountLim {
|
||||
bc.BlocksQueue = append(bc.BlocksQueue, block)
|
||||
return
|
||||
}
|
||||
bc.CountBusy++
|
||||
bc.Lock.Unlock()
|
||||
for {
|
||||
bc.execute(ctx, block)
|
||||
bc.Lock.Lock()
|
||||
if len(bc.BlocksQueue) < 1 {
|
||||
bc.CountBusy--
|
||||
bc.Lock.Unlock()
|
||||
return
|
||||
}
|
||||
block = bc.BlocksQueue[0]
|
||||
bc.BlocksQueue = bc.BlocksQueue[1:]
|
||||
bc.Lock.Unlock()
|
||||
}
|
||||
}
|
56
server/core/blocks_creator.go
Normal file
56
server/core/blocks_creator.go
Normal file
@ -0,0 +1,56 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/matchsystems/werr"
|
||||
"mic-wallet/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IBlocksCreator interface {
|
||||
Run(ctx context.Context) error
|
||||
WriteTx(opts *common.NewTxOpts) error
|
||||
// GetTx Searches for tx in every block
|
||||
GetTx(block int64, hash string) *common.NewTxOpts
|
||||
GetTxs() []*common.NewTxOpts
|
||||
}
|
||||
|
||||
type BlocksCreator struct {
|
||||
blocks map[int64]*Block
|
||||
commiter IBlocksCommiter
|
||||
prevBlockHash []byte
|
||||
}
|
||||
|
||||
func NewBlocksCreator(commiter IBlocksCommiter) *BlocksCreator {
|
||||
return &BlocksCreator{
|
||||
blocks: make(map[int64]*Block),
|
||||
commiter: commiter,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc *BlocksCreator) Run(ctx context.Context) error {
|
||||
var blockID int64
|
||||
for {
|
||||
blockID = common.GetBlockId(time.Now())
|
||||
if oldBlock, ok := bc.blocks[blockID-100]; ok { // Commit and delete old block
|
||||
bc.prevBlockHash = oldBlock.CalcHash()
|
||||
bc.commiter.CommitBlock(ctx, oldBlock)
|
||||
delete(bc.blocks, blockID-100)
|
||||
}
|
||||
bc.blocks[blockID] = NewBlock(blockID, bc.prevBlockHash)
|
||||
time.Sleep(time.Until(time.Unix(blockID+100, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
func (bc *BlocksCreator) WriteTx(opts *common.NewTxOpts) error {
|
||||
currentBlockID := common.GetBlockId(time.Now())
|
||||
if currentBlockID < opts.BlockId {
|
||||
return errors.New("block with such id wasn't created yet. " +
|
||||
"Ensure that you generate block IDs in UTC timezone")
|
||||
} else if currentBlockID > opts.BlockId {
|
||||
return errors.New("block id is out of date")
|
||||
}
|
||||
return werr.Wrapf(bc.blocks[currentBlockID].AddTx(opts),
|
||||
"error adding transaction to block %d", currentBlockID)
|
||||
}
|
10
server/core/commit_block.go
Normal file
10
server/core/commit_block.go
Normal file
@ -0,0 +1,10 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mic-wallet/server/repository"
|
||||
)
|
||||
|
||||
func CommitBlock(ctx context.Context, repo *repository.Repository, block *Block) error {
|
||||
return nil
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
type NewTransactionOpts struct {
|
||||
Hash []byte
|
||||
BlockId int64
|
||||
SenderPublicKey []byte
|
||||
ReceiverPublicKey []byte
|
||||
IsReward bool
|
||||
Amount float64
|
||||
Message string
|
||||
Signature []byte
|
||||
CreatedAt time.Time
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/matchsystems/werr"
|
||||
"math"
|
||||
"mic-wallet/common"
|
||||
"mic-wallet/server/repository/entities"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ValidateNewTransaction(tx *entities.NewTransactionOpts, txHash []byte) error {
|
||||
if bytes.Compare(tx.ReceiverPublicKey, tx.SenderPublicKey) == 0 {
|
||||
return errors.New("cannot send to yourself")
|
||||
}
|
||||
valid, err := common.VerifyTransactionSignature(tx.SenderPublicKey, tx.Signature, txHash, tx.CreatedAt)
|
||||
if err != nil {
|
||||
return werr.Wrapf(err, "failed to verify signature")
|
||||
}
|
||||
if !valid {
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
// Sent data has to be in UTC timezone
|
||||
if time.Duration(math.Abs(float64(tx.CreatedAt.Sub(now)))) > time.Minute*1 {
|
||||
return errors.New("invalid transaction created_at time")
|
||||
}
|
||||
return nil
|
||||
}
|
@ -3,24 +3,27 @@
|
||||
SET TIME ZONE 'UTC';
|
||||
|
||||
CREATE TABLE blocks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
-- UNIX Timestamp
|
||||
-- But not just any timestamp, some timestamp
|
||||
id BIGINT PRIMARY KEY,
|
||||
-- SHA512 sum of all hashes inside the block + prev_hash
|
||||
-- 32 bytes long
|
||||
hash BYTEA UNIQUE,
|
||||
-- NULL only on the first block
|
||||
prev_hash BYTEA UNIQUE,
|
||||
started_at TIMESTAMP NOT NULL,
|
||||
finished_at TIMESTAMP,
|
||||
)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
-- SHA512 -- 32 bytes long
|
||||
hash BYTEA PRIMARY KEY,
|
||||
block_id BIGINT REFERENCES blocks(id) NOT NULL,
|
||||
-- SHA512 -- 64 bytes long
|
||||
hash BYTEA(64) PRIMARY KEY,
|
||||
-- references block id
|
||||
-- but may be added before the block itself
|
||||
block_id BIGINT,
|
||||
|
||||
-- ED25519 32 bytes long
|
||||
sender_public_key BYTEA NOT NULL,
|
||||
receiver_public_key BYTEA NOT NULL,
|
||||
-- ED25519 key -- 32 bytes long
|
||||
sender_public_key BYTEA(32) NOT NULL,
|
||||
receiver_public_key BYTEA(32) NOT NULL,
|
||||
|
||||
-- Rewards doesn't decrease sender's balance
|
||||
-- but generate additional crypto inside the system.
|
||||
@ -34,19 +37,18 @@ CREATE TABLE IF NOT EXISTS transactions (
|
||||
-- When sign is decrypted with the sender's
|
||||
-- public key we should get the created_at
|
||||
-- time in unix format
|
||||
signature BYTEA NOT NULL,
|
||||
signature BYTEA(24) NOT NULL,
|
||||
-- Time provided by the client
|
||||
created_at TIMESTAMP,
|
||||
created_at BIGINT NOT NULL,
|
||||
created_at_ns BIGINT NOT NULL,
|
||||
-- Time when transaction was added to the server
|
||||
added_at TIMESTAMP SET DEFAULT CURRENT TIMESTAMP,
|
||||
|
||||
-- Prevent block duplicates
|
||||
UNIQUE(sendrt_public_key, signature)
|
||||
|
||||
-- NOTE: Will be extended with smart contracts data
|
||||
);
|
||||
|
||||
CREATE INDEX idx_blocks_hash ON blocks (hash);
|
||||
CREATE INDEX idx_blocks_prev_hash ON prev_hash (prev_hash);
|
||||
CREATE INDEX idx_blocks_prev_hash ON blocks (prev_hash);
|
||||
CREATE INDEX idx_transactions_block_id ON transactions (block_id);
|
||||
CREATE INDEX idx_transactions_sender_pk ON transactions (sender_public_key);
|
||||
CREATE INDEX idx_transactions_receiver_pk ON transactions (receiver_public_key);
|
@ -49,6 +49,7 @@ type (
|
||||
Message string `db:"message"`
|
||||
Signature []byte `db:"signature"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
CreatedAtNs int64 `db:"created_at_ns"`
|
||||
AddedAt time.Time `db:"added_at"`
|
||||
}
|
||||
|
||||
@ -63,5 +64,6 @@ type (
|
||||
Message string `db:"message"`
|
||||
Signature []byte `db:"signature"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
CreatedAtNs int64 `db:"created_at_ns"`
|
||||
}
|
||||
)
|
||||
|
@ -18,8 +18,8 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) Setup(ctx context.Context) error {
|
||||
// =============================
|
||||
// Set up the transactions queue
|
||||
// ================================
|
||||
// Set up the transactions pipeline
|
||||
|
||||
// ===================
|
||||
// Setting up database
|
||||
@ -79,7 +79,7 @@ func (s *Server) Setup(ctx context.Context) error {
|
||||
addr := fmt.Sprintf("%s:%d", s.Cfg.Addr, s.Cfg.HttpPort)
|
||||
s.HttpListener, err = net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return werr.Wrapf(err, "failed to listen http on address %s", addr)
|
||||
return werr.Wrapf(err, "failed to listen http_api_entities on address %s", addr)
|
||||
}
|
||||
}
|
||||
if s.Cfg.HttpsPort > 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user