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';
|
SET TIME ZONE 'UTC';
|
||||||
|
|
||||||
CREATE TABLE blocks (
|
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
|
-- SHA512 sum of all hashes inside the block + prev_hash
|
||||||
-- 32 bytes long
|
-- 32 bytes long
|
||||||
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,
|
||||||
started_at TIMESTAMP NOT NULL,
|
|
||||||
finished_at TIMESTAMP,
|
finished_at TIMESTAMP,
|
||||||
)
|
)
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS transactions (
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
-- SHA512 -- 32 bytes long
|
-- SHA512 -- 64 bytes long
|
||||||
hash BYTEA PRIMARY KEY,
|
hash BYTEA(64) PRIMARY KEY,
|
||||||
block_id BIGINT REFERENCES blocks(id) NOT NULL,
|
-- references block id
|
||||||
|
-- but may be added before the block itself
|
||||||
|
block_id BIGINT,
|
||||||
|
|
||||||
-- ED25519 32 bytes long
|
-- ED25519 key -- 32 bytes long
|
||||||
sender_public_key BYTEA NOT NULL,
|
sender_public_key BYTEA(32) NOT NULL,
|
||||||
receiver_public_key BYTEA NOT NULL,
|
receiver_public_key BYTEA(32) NOT NULL,
|
||||||
|
|
||||||
-- Rewards doesn't decrease sender's balance
|
-- Rewards doesn't decrease sender's balance
|
||||||
-- but generate additional crypto inside the system.
|
-- 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
|
-- When sign is decrypted with the sender's
|
||||||
-- public key we should get the created_at
|
-- public key we should get the created_at
|
||||||
-- time in unix format
|
-- time in unix format
|
||||||
signature BYTEA NOT NULL,
|
signature BYTEA(24) NOT NULL,
|
||||||
-- Time provided by the client
|
-- 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
|
-- Time when transaction was added to the server
|
||||||
added_at TIMESTAMP SET DEFAULT CURRENT TIMESTAMP,
|
added_at TIMESTAMP SET DEFAULT CURRENT TIMESTAMP,
|
||||||
|
|
||||||
-- Prevent block duplicates
|
|
||||||
UNIQUE(sendrt_public_key, signature)
|
|
||||||
|
|
||||||
-- NOTE: Will be extended with smart contracts data
|
-- NOTE: Will be extended with smart contracts data
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_blocks_hash ON blocks (hash);
|
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_block_id ON transactions (block_id);
|
||||||
CREATE INDEX idx_transactions_sender_pk ON transactions (sender_public_key);
|
CREATE INDEX idx_transactions_sender_pk ON transactions (sender_public_key);
|
||||||
CREATE INDEX idx_transactions_receiver_pk ON transactions (receiver_public_key);
|
CREATE INDEX idx_transactions_receiver_pk ON transactions (receiver_public_key);
|
@ -49,6 +49,7 @@ type (
|
|||||||
Message string `db:"message"`
|
Message string `db:"message"`
|
||||||
Signature []byte `db:"signature"`
|
Signature []byte `db:"signature"`
|
||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
CreatedAtNs int64 `db:"created_at_ns"`
|
||||||
AddedAt time.Time `db:"added_at"`
|
AddedAt time.Time `db:"added_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,5 +64,6 @@ type (
|
|||||||
Message string `db:"message"`
|
Message string `db:"message"`
|
||||||
Signature []byte `db:"signature"`
|
Signature []byte `db:"signature"`
|
||||||
CreatedAt time.Time `db:"created_at"`
|
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 {
|
func (s *Server) Setup(ctx context.Context) error {
|
||||||
// =============================
|
// ================================
|
||||||
// Set up the transactions queue
|
// Set up the transactions pipeline
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// Setting up database
|
// 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)
|
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 on address %s", addr)
|
return werr.Wrapf(err, "failed to listen http_api_entities on address %s", addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Cfg.HttpsPort > 0 {
|
if s.Cfg.HttpsPort > 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user