232 lines
4.6 KiB
Go
232 lines
4.6 KiB
Go
|
// Package models provides the models for the KermaGo application
|
||
|
package models
|
||
|
|
||
|
import (
|
||
|
"crypto/sha256"
|
||
|
"encoding/binary"
|
||
|
"encoding/hex"
|
||
|
"errors"
|
||
|
"math"
|
||
|
"time"
|
||
|
|
||
|
"github.com/docker/go/canonical/json"
|
||
|
"golang.org/x/exp/slices"
|
||
|
"golang.org/x/exp/utf8string"
|
||
|
)
|
||
|
|
||
|
/*
|
||
|
A Block is a struct that represents a block as per the Kerma specification
|
||
|
*/
|
||
|
type Block struct {
|
||
|
Type string `json:"type" binding:"required"`
|
||
|
Txids []string `json:"txids" binding:"required"`
|
||
|
Nonce string `json:"nonce" binding:"required"`
|
||
|
Previd *string `json:"previd"`
|
||
|
Created int64 `json:"created" binding:"required"`
|
||
|
Target string `json:"T" binding:"required"`
|
||
|
Miner string `json:"miner,omitempty"`
|
||
|
Note string `json:"note,omitempty"`
|
||
|
Height uint64 `json:"-"`
|
||
|
UTXOSet map[string]uint64 `json:"-"`
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Hash returns the sha256 hash of the canonical JSON of the block
|
||
|
*/
|
||
|
func (b *Block) Hash() (string, error) {
|
||
|
block, err := b.MarshalJson()
|
||
|
if err != nil {
|
||
|
return "", errors.New("could not parse block as json")
|
||
|
}
|
||
|
hashSum := sha256.Sum256(block)
|
||
|
|
||
|
return hex.EncodeToString(hashSum[:]), nil
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
GetID returns the block id
|
||
|
*/
|
||
|
func (b *Block) GetID() string {
|
||
|
id, err := b.Hash()
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
return id
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
GetType returns the type of the block
|
||
|
*/
|
||
|
func (b *Block) GetType() string {
|
||
|
return b.Type
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
GetEntity returns the block
|
||
|
*/
|
||
|
func (b *Block) GetEntity() interface{} {
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Validate is responsible for validating the block; returns an error if unsuccessful
|
||
|
*/
|
||
|
func (b *Block) Validate() error {
|
||
|
if b.Type != "block" {
|
||
|
return errors.New("object not a block")
|
||
|
}
|
||
|
|
||
|
if b.IsGenesisBlock() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if b.Previd == nil && !b.IsGenesisBlock() {
|
||
|
return errors.New("illegal genesis block detected")
|
||
|
}
|
||
|
|
||
|
if b.Target != GetGenesisBlock().Target {
|
||
|
return errors.New("incorrect target supplied")
|
||
|
}
|
||
|
|
||
|
if b.Created < GetGenesisBlock().Created {
|
||
|
return errors.New("timestamp before genesis block")
|
||
|
}
|
||
|
|
||
|
// TODO: check if this should be millis
|
||
|
if b.Created > time.Now().UnixMilli() {
|
||
|
return errors.New("block created in the future")
|
||
|
}
|
||
|
|
||
|
if b.Miner != "" {
|
||
|
if !utf8string.NewString(b.Miner).IsASCII() {
|
||
|
return errors.New("miner is not an ascii printable string")
|
||
|
}
|
||
|
|
||
|
if len(b.Miner) > 128 {
|
||
|
return errors.New("miner length incorrect")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if b.Note != "" {
|
||
|
if !utf8string.NewString(b.Note).IsASCII() {
|
||
|
return errors.New("note is not an ascii printable string")
|
||
|
}
|
||
|
|
||
|
if len(b.Note) > 128 {
|
||
|
return errors.New("note length incorrect")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_, err := hex.DecodeString(b.Nonce)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
_, err = hex.DecodeString(*b.Previd)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
bid, err := b.Hash()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
bidBytes, _ := hex.DecodeString(bid)
|
||
|
bint := binary.BigEndian.Uint64(bidBytes)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
tidBytes, _ := hex.DecodeString(b.Target)
|
||
|
tid := binary.BigEndian.Uint64(tidBytes)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if bint >= tid {
|
||
|
return errors.New("proof-of-work equation not satisfied")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
String returns the canonical JSON of the block as a string
|
||
|
*/
|
||
|
func (b *Block) String() string {
|
||
|
res, _ := b.MarshalJson()
|
||
|
return string(res)
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
ContainsTransaction checks if the block contains the specified transaction
|
||
|
*/
|
||
|
func (b *Block) ContainsTransaction(txid string) bool {
|
||
|
return slices.Contains(b.Txids, txid)
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
MarshalJson returns the canonical JSON of the block
|
||
|
*/
|
||
|
func (b *Block) MarshalJson() ([]byte, error) {
|
||
|
return json.MarshalCanonical(b)
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
UnmarshalJSON creates a block from the input JSON byte array
|
||
|
*/
|
||
|
func (b *Block) UnmarshalJSON(data []byte) error {
|
||
|
if len(data) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type tmp Block
|
||
|
err := json.Unmarshal(data, (*tmp)(b))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = b.Validate()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
IsGenesisBlock checks if the block is the genesis block
|
||
|
*/
|
||
|
func (b *Block) IsGenesisBlock() bool {
|
||
|
return b.String() == GetGenesisBlock().String()
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
GetGenesisBlock returns the genesis block
|
||
|
*/
|
||
|
func GetGenesisBlock() *Block {
|
||
|
genesis := Block{
|
||
|
Type: "block",
|
||
|
Target: "00000002af000000000000000000000000000000000000000000000000000000",
|
||
|
Created: 1624219079,
|
||
|
Miner: "dionyziz",
|
||
|
Nonce: "0000000000000000000000000000000000000000000000000000002634878840",
|
||
|
Note: "The Economist 2021-06-20: Crypto-miners are probably to blame for the graphics-chip shortage",
|
||
|
Previd: nil,
|
||
|
Txids: []string{},
|
||
|
Height: 0,
|
||
|
UTXOSet: map[string]uint64{},
|
||
|
}
|
||
|
return &genesis
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
GetBlockReward returns the block reward
|
||
|
*/
|
||
|
func GetBlockReward() uint64 {
|
||
|
// 5 * (10^13)
|
||
|
return 5 * uint64(math.Pow(10, 13))
|
||
|
}
|