ethapi/dag_api.go
package ethapi
import (
"bytes"
"context"
"errors"
"fmt"
"time"
"github.com/beorn7/perks/histogram"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/Fantom-foundation/go-lachesis/hash"
"github.com/Fantom-foundation/go-lachesis/inter"
"github.com/Fantom-foundation/go-lachesis/inter/idx"
)
// PublicDAGChainAPI provides an API to access the directed acyclic graph chain.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicDAGChainAPI struct {
b Backend
}
// NewPublicDAGChainAPI creates a new DAG chain API.
func NewPublicDAGChainAPI(b Backend) *PublicDAGChainAPI {
return &PublicDAGChainAPI{b}
}
// GetEventHeader returns the Lachesis event header by hash or short ID.
func (s *PublicDAGChainAPI) GetEventHeader(ctx context.Context, shortEventID string) (map[string]interface{}, error) {
header, err := s.b.GetEventHeader(ctx, shortEventID)
if err != nil {
return nil, err
}
if header == nil {
return nil, fmt.Errorf("event %s not found", shortEventID)
}
return RPCMarshalEventHeader(header), nil
}
// GetEvent returns Lachesis event by hash or short ID.
func (s *PublicDAGChainAPI) GetEvent(ctx context.Context, shortEventID string, inclTx bool) (map[string]interface{}, error) {
event, err := s.b.GetEvent(ctx, shortEventID)
if err != nil {
return nil, err
}
if event == nil {
return nil, fmt.Errorf("event %s not found", shortEventID)
}
return RPCMarshalEvent(event, inclTx, false)
}
// GetConsensusTime returns event's consensus time, if event is confirmed.
func (s *PublicDAGChainAPI) GetConsensusTime(ctx context.Context, shortEventID string) (inter.Timestamp, error) {
return s.b.GetConsensusTime(ctx, shortEventID)
}
// GetHeads returns IDs of all the epoch events with no descendants.
// * When epoch is -2 the heads for latest epoch are returned.
// * When epoch is -1 the heads for latest sealed epoch are returned.
func (s *PublicDAGChainAPI) GetHeads(ctx context.Context, epoch rpc.BlockNumber) ([]hexutil.Bytes, error) {
res, err := s.b.GetHeads(ctx, epoch)
if err != nil {
return nil, err
}
return eventIDsToHex(res), nil
}
// CurrentEpoch returns current epoch number.
func (s *PublicDAGChainAPI) CurrentEpoch(ctx context.Context) hexutil.Uint64 {
return hexutil.Uint64(s.b.CurrentEpoch(ctx))
}
// GetEpochStats returns epoch statistics.
// * When epoch is -2 the statistics for latest epoch is returned.
// * When epoch is -1 the statistics for latest sealed epoch is returned.
func (s *PublicDAGChainAPI) GetEpochStats(ctx context.Context, requestedEpoch rpc.BlockNumber) (map[string]interface{}, error) {
stats, err := s.b.GetEpochStats(ctx, requestedEpoch)
if err != nil {
return nil, err
}
if stats == nil {
return nil, nil
}
return map[string]interface{}{
"epoch": hexutil.Uint64(stats.Epoch),
"start": hexutil.Uint64(stats.Start),
"end": hexutil.Uint64(stats.End),
"totalFee": (*hexutil.Big)(stats.TotalFee),
"totalBaseRewardWeight": (*hexutil.Big)(stats.TotalBaseRewardWeight),
"totalTxRewardWeight": (*hexutil.Big)(stats.TotalTxRewardWeight),
}, nil
}
func durationToRPC(t time.Duration) string {
/*if t < 0 {
t = -t
return "-" + hexutil.Uint64(t).String()
}
return hexutil.Uint64(t).String()*/
return t.String()
}
func rpcEncodeEventsTimeStats(data map[hash.Event]time.Duration, verbosity hexutil.Uint64) (map[string]interface{}, error) {
resRPC := map[string]interface{}{}
hist := histogram.New(6)
if verbosity >= 2 {
hist = histogram.New(16)
}
raw := map[string]interface{}{}
totalTtf := time.Duration(0)
minTtf := time.Duration(0)
maxTtf := time.Duration(0)
for id, ttf := range data {
raw[id.FullID()] = durationToRPC(ttf)
// stats
totalTtf += ttf
if minTtf == 0 || minTtf > ttf {
minTtf = ttf
}
if maxTtf == 0 || maxTtf < ttf {
maxTtf = ttf
}
hist.Insert(float64(ttf))
}
avgTtf := totalTtf
if len(data) > 0 {
avgTtf /= time.Duration(len(data))
}
{
stats := map[string]interface{}{}
stats["avg"] = durationToRPC(avgTtf)
stats["min"] = durationToRPC(minTtf)
stats["max"] = durationToRPC(maxTtf)
stats["samples"] = hexutil.Uint64(len(data))
resRPC["stats"] = stats
}
if verbosity >= 1 {
histRPC := make([]map[string]interface{}, len(hist.Bins()))
for i, bin := range hist.Bins() {
histRPC[i] = map[string]interface{}{}
histRPC[i]["count"] = hexutil.Uint64(bin.Count)
histRPC[i]["mean"] = durationToRPC(time.Duration(bin.Mean()))
}
resRPC["histogram"] = histRPC
}
if verbosity >= 3 {
resRPC["raw"] = raw
}
return resRPC, nil
}
// BlocksTTF for a range of blocks
// maxBlocks. Number. maximum number of blocks to process
// Mode. String. One of {"arrival_time", "claimed_time"}
// Verbosity. Number. If 0, then include only avg, min, max.
// Verbosity. Number. If >= 1, then include histogram with 6 bins.
// Verbosity. Number. If >= 2, then include histogram with 16 bins.
// Verbosity. Number. If >= 3, then include raw data.
func (s *PublicDebugAPI) BlocksTTF(ctx context.Context, untilBlock rpc.BlockNumber, maxBlocks hexutil.Uint64, mode string, verbosity hexutil.Uint64) (map[string]interface{}, error) {
if mode != "arrival_time" && mode != "claimed_time" {
return nil, errors.New("mode must be one of {arrival_time, claimed_time}")
}
ttfs, err := s.b.BlocksTTF(ctx, untilBlock, idx.Block(maxBlocks), mode)
if err != nil {
return nil, err
}
return rpcEncodeEventsTimeStats(ttfs, verbosity)
}
// ValidatorVersions returns published node version of each validator.
// If validator didn't have an event in beginning of epoch, then it will not be listed.
func (s *PublicDebugAPI) ValidatorVersions(ctx context.Context, epoch rpc.BlockNumber, maxEvents hexutil.Uint64) (map[hexutil.Uint64]string, error) {
processed := 0
prefix := []byte("v-")
versions := map[hexutil.Uint64]string{}
err := s.b.ForEachEpochEvent(ctx, epoch, func(event *inter.Event) bool {
creator := hexutil.Uint64(event.Creator)
if bytes.HasPrefix(event.Extra, prefix) {
version := string(event.Extra[len(prefix):])
versions[creator] = version
} else if _, ok := versions[creator]; !ok {
versions[creator] = "not found"
}
processed++
return processed < int(maxEvents) // iterate until first met event with high seq
})
return versions, err
}
// ValidatorTimeDrifts for an epoch
// maxEvents. Number. maximum number of events to process
// Verbosity. Number. If 0, then include only avg, min, max.
// Verbosity. Number. If >= 1, then include histogram with 6 bins.
// Verbosity. Number. If >= 2, then include histogram with 16 bins.
// Verbosity. Number. If >= 3, then include raw data.
func (s *PublicDebugAPI) ValidatorTimeDrifts(ctx context.Context, epoch rpc.BlockNumber, maxEvents hexutil.Uint64, verbosity hexutil.Uint64) (map[hexutil.Uint64]map[string]interface{}, error) {
validatorsDrifts, err := s.b.ValidatorTimeDrifts(ctx, epoch, idx.Event(maxEvents))
resRPC := map[hexutil.Uint64]map[string]interface{}{}
for v, drifts := range validatorsDrifts {
vRPC, err := rpcEncodeEventsTimeStats(drifts, verbosity)
if err != nil {
return nil, err
}
resRPC[hexutil.Uint64(v)] = vRPC
}
return resRPC, err
}