Fantom-foundation/go-lachesis

View on GitHub
inter/event.go

Summary

Maintainability
A
0 mins
Test Coverage
package inter

import (
    "bytes"
    "crypto/ecdsa"
    "fmt"
    "sync/atomic"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/rlp"
    "github.com/ethereum/go-ethereum/trie"
    "golang.org/x/crypto/sha3"

    "github.com/Fantom-foundation/go-lachesis/hash"
    "github.com/Fantom-foundation/go-lachesis/inter/idx"
)

var (
    // EmptyTxHash is hash of empty transactions list. Used to check that event doesn't have transactions not having full event.
    EmptyTxHash = types.DeriveSha(types.Transactions{}, new(trie.Trie))
)

// EventHeaderData is the graph vertex in the Lachesis consensus algorithm
// Doesn't contain transactions, only their hash
// Doesn't contain event signature
type EventHeaderData struct {
    Version uint32 // serialization version

    Epoch idx.Epoch
    Seq   idx.Event

    Frame  idx.Frame
    IsRoot bool

    Creator idx.StakerID

    PrevEpochHash common.Hash
    Parents       hash.Events

    GasPowerLeft GasPowerLeft
    GasPowerUsed uint64

    Lamport     idx.Lamport
    ClaimedTime Timestamp
    MedianTime  Timestamp

    TxHash common.Hash

    Extra []byte

    // caches
    hash atomic.Value
}

// EventHeader is the graph vertex in the Lachesis consensus algorithm
// Doesn't contain transactions, only their hash
type EventHeader struct {
    EventHeaderData

    Sig []byte
}

// Event is the graph vertex in the Lachesis consensus algorithm
type Event struct {
    EventHeader
    Transactions types.Transactions

    // caches
    size atomic.Value
}

// NewEvent constructs empty event
func NewEvent() *Event {
    return &Event{
        EventHeader: EventHeader{
            EventHeaderData: EventHeaderData{
                Extra: []byte{},
            },
            Sig: []byte{},
        },
        Transactions: types.Transactions{},
    }
}

// FmtFrame returns string representation.
func FmtFrame(frame idx.Frame, isRoot bool) string {
    if isRoot {
        return fmt.Sprintf("%d:y", frame)
    }
    return fmt.Sprintf("%d:n", frame)
}

// String returns string representation.
func (e *EventHeaderData) String() string {
    return fmt.Sprintf("{id=%s, p=%s, by=%d, frame=%s}", e.Hash().ShortID(3), e.Parents.String(), e.Creator, FmtFrame(e.Frame, e.IsRoot))
}

// NoTransactions is used to check that event doesn't have transactions not having full event.
func (e *EventHeaderData) NoTransactions() bool {
    return e.TxHash == EmptyTxHash
}

// DataToSign returns data which must be signed to sign the event
func (e *EventHeaderData) DataToSign() []byte {
    buf := bytes.NewBuffer([]byte{})
    buf.Write([]byte("Lachesis: I'm signing the Event "))
    buf.Write(e.calcHash().Bytes())
    return buf.Bytes()
}

// SelfParent returns event's self-parent, if any
func (e *EventHeaderData) SelfParent() *hash.Event {
    if e.Seq <= 1 || len(e.Parents) == 0 {
        return nil
    }
    return &e.Parents[0]
}

// IsSelfParent is true if specified ID is event's self-parent
func (e *EventHeaderData) IsSelfParent(hash hash.Event) bool {
    if e.SelfParent() == nil {
        return false
    }
    return *e.SelfParent() == hash
}

// SignBy signs event by private key.
func (e *Event) SignBy(priv *ecdsa.PrivateKey) error {
    signer := func(data []byte) ([]byte, error) {
        data = crypto.Keccak256(data)
        sig, err := crypto.Sign(data, priv)
        return sig, err
    }

    return e.Sign(signer)
}

// Sign event by signer.
func (e *Event) Sign(signer func([]byte) ([]byte, error)) error {
    e.RecacheHash() // because HashToSign uses .Hash
    sig, err := signer(e.DataToSign())
    if err != nil {
        return err
    }

    e.Sig = sig
    return nil
}

func (e *Event) RecoverPubkey() *ecdsa.PublicKey {
    // NOTE: Keccak256 because of AccountManager
    signedHash := crypto.Keccak256(e.DataToSign())
    pk, err := crypto.SigToPub(signedHash, e.Sig)
    if err != nil {
        return nil
    }
    return pk
}

// VerifySignature checks the signature against e.Creator.
func (e *Event) VerifySignature(address common.Address) bool {
    // NOTE: Keccak256 because of AccountManager
    signedHash := crypto.Keccak256(e.DataToSign())
    pk, err := crypto.SigToPub(signedHash, e.Sig)
    if err != nil {
        return false
    }
    return crypto.PubkeyToAddress(*pk) == address
}

/*
 * Event ID (hash):
 */

func (e *EventHeaderData) calcHash() hash.Event {
    hasher := sha3.NewLegacyKeccak256()
    err := rlp.Encode(hasher, e)
    if err != nil {
        panic("can't encode: " + err.Error())
    }
    return hash.BytesToEvent(hasher.Sum(nil))
}

// CalcHash re-calculates event's ID
func (e *EventHeaderData) CalcHash() hash.Event {
    id := e.calcHash()
    // return 24 bytes hash | epoch | lamport
    copy(id[0:4], e.Epoch.Bytes())
    copy(id[4:8], e.Lamport.Bytes())
    return id
}

// RecacheHash re-calculates event's ID and caches it
func (e *EventHeaderData) RecacheHash() hash.Event {
    id := e.CalcHash()
    e.hash.Store(id)
    return id
}

// Hash returns cached event ID
func (e *EventHeaderData) Hash() hash.Event {
    if cached := e.hash.Load(); cached != nil {
        return cached.(hash.Event)
    }
    return e.RecacheHash()
}

/*
 * Event size:
 */

type writeCounter int

// Write only counts "written" bytes
func (c *writeCounter) Write(b []byte) (int, error) {
    *c += writeCounter(len(b))
    return len(b), nil
}

// CalcSize re-calculates event's size
func (e *Event) CalcSize() int {
    c := writeCounter(0)
    _ = rlp.Encode(&c, e)
    return int(c)
}

// RecacheSize re-calculates event's size and caches it
func (e *Event) RecacheSize() int {
    size := e.CalcSize()
    e.size.Store(size)
    return size
}

// Size returns cached event size
func (e *Event) Size() int {
    if cached := e.size.Load(); cached != nil {
        return cached.(int)
    }
    return e.RecacheSize()
}

/*
 * Utils:
 */

// FakeFuzzingEvents generates random independent events with the same epoch for testing purpose.
func FakeFuzzingEvents() (res []*Event) {
    creators := []idx.StakerID{
        0,
        hash.FakePeer(),
        hash.FakePeer(),
        hash.FakePeer(),
    }
    parents := []hash.Events{
        hash.FakeEvents(1),
        hash.FakeEvents(2),
        hash.FakeEvents(8),
    }
    i := 0
    for c := 0; c < len(creators); c++ {
        for p := 0; p < len(parents); p++ {
            e := NewEvent()
            e.Epoch = hash.FakeEpoch()
            e.Seq = idx.Event(p)
            e.Creator = creators[c]
            e.Parents = parents[p]
            e.Extra = []byte{}
            e.Sig = []byte{}

            res = append(res, e)
            i++
        }
    }
    return
}

// GasPowerLeft is long-term gas power left and short-term gas power left
type GasPowerLeft struct {
    Gas [2]uint64
}

// Add add to all gas power lefts
func (g *GasPowerLeft) Add(diff uint64) {
    for i := range g.Gas {
        g.Gas[i] += diff
    }
}

// Min returns minimum within long-term gas power left and short-term gas power left
func (g *GasPowerLeft) Min() uint64 {
    min := g.Gas[0]
    for _, gas := range g.Gas {
        if min > gas {
            min = gas
        }
    }
    return min
}

// Max returns maximum within long-term gas power left and short-term gas power left
func (g *GasPowerLeft) Max() uint64 {
    max := g.Gas[0]
    for _, gas := range g.Gas {
        if max < gas {
            max = gas
        }
    }
    return max
}

// Sub subtracts from all gas power lefts
func (g *GasPowerLeft) Sub(diff uint64) *GasPowerLeft {
    for i := range g.Gas {
        g.Gas[i] -= diff
    }
    return g
}

// String returns string representation.
func (g *GasPowerLeft) String() string {
    return fmt.Sprintf("{short=%d, long=%d}", g.Gas[idx.ShortTermGas], g.Gas[idx.LongTermGas])
}