micw/common/block.go
2024-11-24 15:04:52 +01:00

139 lines
3.7 KiB
Go

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 {
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 {
Id int64
PrevHash []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()
}