aergoio/aergo

View on GitHub
types/blockchain.go

Summary

Maintainability
A
2 hrs
Test Coverage
F
53%
/**
 *  @file
 *  @copyright defined in aergo/LICENSE.txt
 */

package types

import (
    "bytes"
    "encoding/binary"
    "io"
    "math"
    "math/big"
    "reflect"
    "runtime"
    "sync/atomic"
    "time"

    "github.com/aergoio/aergo/v2/internal/common"
    "github.com/aergoio/aergo/v2/internal/enc/base58"
    "github.com/aergoio/aergo/v2/internal/enc/proto"
    "github.com/aergoio/aergo/v2/internal/merkle"
    "github.com/libp2p/go-libp2p-core/crypto"
    "github.com/minio/sha256-simd"
)

const (
    // DefaultMaxBlockSize is the maximum block size (currently 1MiB)
    DefaultMaxBlockSize = 1 << 20
    DefaultEvictPeriod  = 12

    // DefaultMaxHdrSize is the max size of the proto-buf serialized non-body
    // fields. For the estimation detail, check 'TestBlockHeaderLimit' in
    // 'blockchain_test.go.' Caution: Be sure to adjust the value below if the
    // structure of the header is changed.
    DefaultMaxHdrSize = 400
    lastFieldOfBH     = "Consensus"
)

type TxHash = []byte
type AvgTime struct {
    val  atomic.Value
    mavg *MovingAverage
}

var (
    DefaultVerifierCnt = int(math.Max(float64(runtime.NumCPU()/2), float64(1)))
    DefaultAvgTimeSize = 60 * 60 * 24
    AvgTxVerifyTime    = NewAvgTime(DefaultAvgTimeSize)
    // MaxAER is maximum value of aergo
    MaxAER *big.Int
    // StakingMinimum is minimum amount for staking
    StakingMinimum *big.Int
    // ProposalPrice is default value of creating proposal
    ProposalPrice *big.Int
    lastIndexOfBH int
)

func init() {
    MaxAER = NewAmount(5*1e8, Aergo)       // 500,000,000 aergo
    StakingMinimum = NewAmount(1e4, Aergo) // 10,000 aergo
    ProposalPrice = NewZeroAmount()        // 0 aergo
    lastIndexOfBH = getLastIndexOfBH()
}

func NewAvgTime(sizeMavg int) *AvgTime {
    avgTime := &AvgTime{}
    avgTime.mavg = NewMovingAverage(sizeMavg)
    avgTime.val.Store(time.Duration(0))
    return avgTime
}

func (avgTime *AvgTime) Get() time.Duration {
    var avg time.Duration
    aopv := avgTime.val.Load()
    if aopv != nil {
        avg = aopv.(time.Duration)
    } else {
        panic("AvgTxSignTime is not set")
    }
    return avg
}

func (avgTime *AvgTime) UpdateAverage(cur time.Duration) time.Duration {
    newAvg := time.Duration(avgTime.mavg.Add(int64(cur)))
    avgTime.set(newAvg)

    return newAvg
}

func (avgTime *AvgTime) set(val time.Duration) {
    avgTime.val.Store(val)
}

func getLastIndexOfBH() (lastIndex int) {
    v := reflect.ValueOf(BlockHeader{})

    nField := v.NumField()
    var i int
    for i = 0; i < nField; i++ {
        name := v.Type().Field(i).Name
        if name == lastFieldOfBH {
            lastIndex = i
            break
        }
    }

    return i
}

//go:generate stringer -type=SystemValue
type SystemValue int

const (
    StakingTotal SystemValue = 0 + iota
    StakingMin
    GasPrice
    NamePrice
    TotalVotingPower
    VotingReward
)

/*
  func (s SystemValue) String() string {
      return [...]string{"StakingTotal", "StakingMin", "GasPrice", "NamePrice"}[s]
  }
*/

// ChainAccessor is an interface for a another actor module to get info of chain
type ChainAccessor interface {
    GetGenesisInfo() *Genesis
    GetConsensusInfo() string
    GetBestBlock() (*Block, error)
    // GetBlock return block of blockHash. It return nil and error if not found block of that hash or there is a problem in db store
    GetBlock(blockHash []byte) (*Block, error)
    // GetHashByNo returns hash of block. It return nil and error if not found block of that number or there is a problem in db store
    GetHashByNo(blockNo BlockNo) ([]byte, error)
    GetChainStats() string
    GetSystemValue(key SystemValue) (*big.Int, error)

    // GetEnterpriseConfig always return non-nil object if there is no error, but it can return EnterpriseConfig with empty values
    GetEnterpriseConfig(key string) (*EnterpriseConfig, error)
    ChainID(bno BlockNo) *ChainID
}

type SyncContext struct {
    Seq uint64

    PeerID PeerID

    BestNo   BlockNo
    TargetNo BlockNo //sync target blockno

    CommonAncestor *Block

    TotalCnt   uint64
    RemainCnt  uint64
    LastAnchor BlockNo

    NotifyC chan error
}

func NewSyncCtx(seq uint64, peerID PeerID, targetNo uint64, bestNo uint64, notifyC chan error) *SyncContext {
    return &SyncContext{Seq: seq, PeerID: peerID, TargetNo: targetNo, BestNo: bestNo, LastAnchor: 0, NotifyC: notifyC}
}

func (ctx *SyncContext) SetAncestor(ancestor *Block) {
    ctx.CommonAncestor = ancestor
    ctx.TotalCnt = ctx.TargetNo - ctx.CommonAncestor.BlockNo()
    ctx.RemainCnt = ctx.TotalCnt
}

// BlockInfo is used for actor message to send block info
type BlockInfo struct {
    Hash []byte
    No   BlockNo
}

func (bi *BlockInfo) Equal(target *BlockInfo) bool {
    if target == nil {
        return false
    }

    if bi.No == target.No && bytes.Equal(bi.Hash, target.Hash) {
        return true
    } else {
        return false
    }
}

// BlockNo is the height of a block, which starts from 0 (genesis block).
type BlockNo = uint64

func Uint64ToBytes(num uint64) []byte {
    buf := make([]byte, 8)
    binary.LittleEndian.PutUint64(buf, num)
    return buf
}

func BytesToUint64(data []byte) uint64 {
    buf := binary.LittleEndian.Uint64(data)
    return buf
}

// BlockNoToBytes represents to serialize block no to bytes
func BlockNoToBytes(bn BlockNo) []byte {
    return Uint64ToBytes(bn)
}

// BlockNoFromBytes represents to deserialize bytes to block no
func BlockNoFromBytes(raw []byte) BlockNo {
    buf := BytesToUint64(raw)
    return BlockNo(buf)
}

type BlockVersionner interface {
    Version(no BlockNo) int32
    IsV2Fork(BlockNo) bool
}

type DummyBlockVersionner int32

func (v DummyBlockVersionner) Version(BlockNo) int32 {
    return int32(v)
}

func (v DummyBlockVersionner) IsV2Fork(BlockNo) bool {
    return (v >= 2)
}

// NewBlock represents to create a block to store transactions.
func NewBlock(bi *BlockHeaderInfo, blockRoot []byte, receipts *Receipts, txs []*Tx, coinbaseAcc []byte, consensus []byte) *Block {
    return &Block{
        Header: &BlockHeader{
            ChainID:          bi.ChainId,
            PrevBlockHash:    bi.PrevBlockHash,
            BlockNo:          bi.No,
            Timestamp:        bi.Ts,
            BlocksRootHash:   blockRoot,
            TxsRootHash:      CalculateTxsRootHash(txs),
            ReceiptsRootHash: receipts.MerkleRoot(),
            CoinbaseAccount:  coinbaseAcc,
            Consensus:        consensus,
        },
        Body: &BlockBody{
            Txs: txs,
        },
    }
}

// Localtime retrurns a time.Time object, which is coverted from block
// timestamp.
func (block *Block) Localtime() time.Time {
    return time.Unix(0, block.GetHeader().GetTimestamp())
}

// calculateBlockHash computes sha256 hash of block header.
func (block *Block) calculateBlockHash() []byte {
    digest := sha256.New()
    writeBlockHeader(digest, block.Header)
    return digest.Sum(nil)
}

func writeBlockHeader(w io.Writer, bh *BlockHeader) error {
    for _, f := range []interface{}{
        bh.ChainID,
        bh.PrevBlockHash,
        bh.BlockNo,
        bh.Timestamp,
        bh.BlocksRootHash,
        bh.TxsRootHash,
        bh.ReceiptsRootHash,
        bh.Confirms,
        bh.PubKey,
        bh.CoinbaseAccount,
        bh.Sign,
        bh.Consensus,
    } {
        if err := binary.Write(w, binary.LittleEndian, f); err != nil {
            return err
        }
    }

    return nil
}

func writeBlockHeaderOmitSign(w io.Writer, bh *BlockHeader) error {
    for _, f := range []interface{}{
        bh.ChainID,
        bh.PrevBlockHash,
        bh.BlockNo,
        bh.Timestamp,
        bh.BlocksRootHash,
        bh.TxsRootHash,
        bh.ReceiptsRootHash,
        bh.Confirms,
        bh.PubKey,
        bh.CoinbaseAccount,
        // bh.Sign, // omit ignore sign value
        bh.Consensus,
    } {
        if err := binary.Write(w, binary.LittleEndian, f); err != nil {
            return err
        }
    }

    return nil
}

// BlockHash returns block hash. It returns a calculated value if the hash is nil.
func (block *Block) BlockHash() []byte {
    hash := block.GetHash()
    if len(hash) == 0 {
        block.Hash = block.calculateBlockHash()
    }

    return block.GetHash()
}

// BlockID converts block hash ([]byte) to BlockID.
func (block *Block) BlockID() BlockID {
    return ToBlockID(block.BlockHash())
}

// PrevBlockID converts parent block hash ([]byte) to BlockID.
func (block *Block) PrevBlockID() BlockID {
    return ToBlockID(block.GetHeader().GetPrevBlockHash())
}

// SetChainID sets id to block.ChainID
func (block *Block) SetChainID(id []byte) {
    block.Header.ChainID = id
}

// ValidChildOf reports whether block is a varid child of parent.
func (block *Block) ValidChildOf(parent *Block) bool {
    parChainID := parent.GetHeader().GetChainID()
    curChainID := block.GetHeader().GetChainID()

    // empty chain id case: an older verion of block has no chain id in its
    // block header.
    if len(parChainID) == 0 && len(curChainID) == 0 {
        return true
    }

    return ChainIdEqualWithoutVersion(parChainID, curChainID)
}

// Size returns a block size where the tx size is individually calculated. A
// similar method is used to limit the block size by the block factory.
//
// THE REASON WHY THE BLOCK FACTORY DOESN'T USE THE EXACT SIZE OF A MARSHALED
// BLOCK: The actual size of a marshaled block is larger than this because it
// includes an additional data associated with the marshaling of the
// transations array in the block body. It is ineffective that the (DPoS) block
// factory measures the exact size of the additional probuf data when it
// produces a block. Thus we use the slightly(?) different and less expensive
// estimation of the block size.
func (block *Block) Size() int {
    size := proto.Size(block.GetHeader()) + len(block.GetHash())
    for _, tx := range block.GetBody().GetTxs() {
        size += proto.Size(tx)
    }
    return size
}

// Confirms returns block.Header.Confirms which indicates how many block is confirmed
// by block.
func (block *Block) Confirms() BlockNo {
    return block.GetHeader().GetConfirms()
}

// SetConfirms sets block.Header.Confirms to confirms.
func (block *Block) SetConfirms(confirms BlockNo) {
    block.Header.Confirms = confirms
}

// BlockNo returns the block number of block.
func (block *Block) BlockNo() BlockNo {
    return block.GetHeader().GetBlockNo()
}

// Sign adds a pubkey and a block signature to block.
func (block *Block) Sign(privKey crypto.PrivKey) error {
    var err error

    if err = block.setPubKey(privKey.GetPublic()); err != nil {
        return err
    }

    var msg []byte
    if msg, err = block.Header.bytesForDigest(); err != nil {
        return err
    }

    var sig []byte
    if sig, err = privKey.Sign(msg); err != nil {
        return err
    }
    block.Header.Sign = sig

    return nil
}

func (bh *BlockHeader) bytesForDigest() ([]byte, error) {
    var buf bytes.Buffer

    if err := writeBlockHeaderOmitSign(&buf, bh); err != nil {
        return nil, err
    }

    return buf.Bytes(), nil
}

// VerifySign verifies the signature of block.
func (block *Block) VerifySign() (valid bool, err error) {
    var pubKey crypto.PubKey
    if pubKey, err = crypto.UnmarshalPublicKey(block.Header.PubKey); err != nil {
        return false, err
    }

    var msg []byte
    if msg, err = block.Header.bytesForDigest(); err != nil {
        return false, err
    }

    if valid, err = pubKey.Verify(msg, block.Header.Sign); err != nil {
        return
    }

    return valid, nil
}

// BPID returns its Block Producer's ID from block.
func (block *Block) BPID() (id PeerID, err error) {
    return block.Header.BPID()
}

// BpID2Str returns its Block Producer's ID in base64 format.
func (block *Block) BPID2Str() string {
    id, err := block.BPID()
    if err != nil {
        return ""
    }

    return base58.Encode([]byte(id))
}

// ID returns the base64 encoded formated ID (hash) of block.
func (block *Block) ID() string {
    hash := block.BlockHash()
    if hash != nil {
        return base58.Encode(hash)
    }

    return ""

}

// PrevID returns the base64 encoded formated ID (hash) of the parent block.
func (block *Block) PrevID() string {
    hash := block.GetHeader().GetPrevBlockHash()
    if hash != nil {
        return base58.Encode(hash)
    }

    return ""

}

// SetPubKey sets block.Header.PubKey to pubkey.
func (block *Block) setPubKey(pubKey crypto.PubKey) error {
    var pk []byte
    var err error
    if pk, err = crypto.MarshalPublicKey(pubKey); err != nil {
        return err
    }
    block.Header.PubKey = pk

    return nil
}

func (block *Block) SetBlocksRootHash(blockRootHash []byte) {
    block.GetHeader().BlocksRootHash = blockRootHash
}

// GetMetadata generates Metadata object for block
func (block *Block) GetMetadata() *BlockMetadata {
    return &BlockMetadata{
        Hash:    block.BlockHash(),
        Header:  block.GetHeader(),
        Txcount: int32(len(block.GetBody().GetTxs())),
        Size:    int64(proto.Size(block)),
    }
}

// CalculateTxsRootHash generates merkle tree of transactions and returns root hash.
func CalculateTxsRootHash(txs []*Tx) []byte {
    mes := make([]merkle.MerkleEntry, len(txs))
    for i, tx := range txs {
        mes[i] = tx
    }
    return merkle.CalculateMerkleRoot(mes)
}

func NewTx() *Tx {
    tx := &Tx{
        Body: &TxBody{
            Nonce: uint64(1),
        },
    }
    return tx
}

func (tx *Tx) CalculateTxHash() []byte {
    txBody := tx.Body
    digest := sha256.New()
    binary.Write(digest, binary.LittleEndian, txBody.Nonce)

    digest.Write(txBody.Account)
    digest.Write(txBody.Recipient)
    digest.Write(txBody.Amount)
    digest.Write(txBody.Payload)
    binary.Write(digest, binary.LittleEndian, txBody.GasLimit)
    digest.Write(txBody.GasPrice)
    binary.Write(digest, binary.LittleEndian, txBody.Type)
    digest.Write(txBody.ChainIdHash)
    digest.Write(txBody.Sign)
    return digest.Sum(nil)
}

func (tx *Tx) NeedNameVerify() bool {
    return tx.HasNameAccount()
}

func (tx *Tx) HasNameAccount() bool {
    return len(tx.Body.Account) <= NameLength
}

func (tx *Tx) HasNameRecipient() bool {
    return tx.Body.Recipient != nil && len(tx.Body.Recipient) <= NameLength
}

func (tx *Tx) Clone() *Tx {
    if tx == nil {
        return nil
    }
    if tx.Body == nil {
        return &Tx{}
    }
    body := &TxBody{
        Nonce:       tx.Body.Nonce,
        Account:     tx.Body.Account,
        Recipient:   tx.Body.Recipient,
        Amount:      tx.Body.Amount,
        Payload:     tx.Body.Payload,
        GasLimit:    tx.Body.GasLimit,
        GasPrice:    tx.Body.GasPrice,
        Type:        tx.Body.Type,
        ChainIdHash: tx.Body.ChainIdHash,
        Sign:        tx.Body.Sign,
    }
    res := &Tx{
        Body: body,
    }
    res.Hash = res.CalculateTxHash()
    return res
}

func (b *TxBody) GetAmountBigInt() *big.Int {
    return new(big.Int).SetBytes(b.GetAmount())
}

func (b *TxBody) GetGasPriceBigInt() *big.Int {
    return new(big.Int).SetBytes(b.GetGasPrice())
}

type MovingAverage struct {
    values []int64
    size   int
    count  int

    sum        int64
    removedVal int64
    curPos     int
}

func NewMovingAverage(size int) *MovingAverage {
    return &MovingAverage{
        values:     make([]int64, size),
        size:       size,
        removedVal: 0,
        sum:        0,
        curPos:     -1,
        count:      0,
    }
}

func (ma *MovingAverage) Add(val int64) int64 {
    ma.curPos = (ma.curPos + 1) % ma.size
    ma.removedVal = ma.values[ma.curPos]
    ma.values[ma.curPos] = val

    if ma.count != ma.size {
        ma.count++
    }

    return ma.calculateAvg()
}

func (ma *MovingAverage) calculateAvg() int64 {
    //values is empty
    if ma.count == 0 {
        return 0
    }

    ma.sum = ma.sum - ma.removedVal + ma.values[ma.curPos]

    // Finalize average and return
    avg := ma.sum / int64(ma.count)
    return avg
}

type BlockHeaderInfo struct {
    No            BlockNo
    Ts            int64
    PrevBlockHash []byte
    ChainId       []byte
    ForkVersion   int32
}

var EmptyBlockHeaderInfo = &BlockHeaderInfo{}

func NewBlockHeaderInfo(b *Block) *BlockHeaderInfo {
    cid := b.GetHeader().GetChainID()
    v := DecodeChainIdVersion(cid)
    return &BlockHeaderInfo{
        b.BlockNo(),
        b.GetHeader().GetTimestamp(),
        b.GetHeader().GetPrevBlockHash(),
        cid,
        v,
    }
}

func MakeChainId(cid []byte, v int32) []byte {
    nv := ChainIdVersion(v)
    if bytes.Equal(cid[:4], nv) {
        return cid
    }
    newCid := make([]byte, len(cid))
    copy(newCid, nv)
    copy(newCid[4:], cid[4:])
    return newCid
}

func NewBlockHeaderInfoFromPrevBlock(prev *Block, ts int64, bv BlockVersionner) *BlockHeaderInfo {
    no := prev.GetHeader().GetBlockNo() + 1
    cid := prev.GetHeader().GetChainID()
    v := bv.Version(no)
    return &BlockHeaderInfo{
        no,
        ts,
        prev.GetHash(),
        MakeChainId(cid, v),
        v,
    }
}

func (b *BlockHeaderInfo) ChainIdHash() []byte {
    return common.Hasher(b.ChainId)
}

func (bh *BlockHeader) BPID() (id PeerID, err error) {
    var pubKey crypto.PubKey
    if pubKey, err = crypto.UnmarshalPublicKey(bh.PubKey); err != nil {
        return PeerID(""), err
    }
    if id, err = IDFromPublicKey(pubKey); err != nil {
        return PeerID(""), err
    }
    return
}

// HasFunction returns if a function with the given name exists in the ABI definition
func (abi *ABI) HasFunction(name string) bool {
    for _, fn := range abi.Functions {
        if fn.GetName() == name {
            return true
        }
    }
    return false
}