// 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)) }