erigon-pulse/cmd/devnet/services/bor/heimdall.go
Mark Holt 529d359ca6
Bor span testing (#7897)
An update to the devnet to introduce a local heimdall to facilitate
multiple validators without the need for an external process, and hence
validator registration/staking etc.

In this initial release only span generation is supported.  

It has the following changes:

* Introduction of a local grpc heimdall interface
* Allocation of accounts via a devnet account generator ()
* Introduction on 'Services' for the network config

"--chain bor-devnet --bor.localheimdall" will run a 2 validator network
with a local service
"--chain bor-devnet --bor.withoutheimdall" will sun a single validator
with no heimdall service as before

---------

Co-authored-by: Alex Sharp <alexsharp@Alexs-MacBook-Pro-2.local>
2023-07-18 09:47:04 +01:00

251 lines
6.3 KiB
Go

package bor
import (
"context"
"fmt"
"math/big"
"strings"
"sync"
"github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/accounts/abi/bind"
"github.com/ledgerwatch/erigon/cmd/devnet/accounts"
"github.com/ledgerwatch/erigon/cmd/devnet/contracts"
"github.com/ledgerwatch/erigon/cmd/devnet/devnet"
"github.com/ledgerwatch/erigon/cmd/devnet/requests"
"github.com/ledgerwatch/erigon/consensus/bor/clerk"
"github.com/ledgerwatch/erigon/consensus/bor/heimdall/checkpoint"
"github.com/ledgerwatch/erigon/consensus/bor/heimdall/span"
"github.com/ledgerwatch/erigon/consensus/bor/heimdallgrpc"
"github.com/ledgerwatch/erigon/consensus/bor/valset"
"github.com/ledgerwatch/log/v3"
)
type Heimdall struct {
sync.Mutex
currentSpan *span.HeimdallSpan
chainConfig *chain.Config
validatorSet *valset.ValidatorSet
spans map[uint64]*span.HeimdallSpan
logger log.Logger
cancelFunc context.CancelFunc
syncChan chan<- *contracts.TestStateSenderStateSynced
syncContractAddress libcommon.Address
syncContractBinding *contracts.TestStateSender
}
func NewHeimdall(chainConfig *chain.Config, logger log.Logger) *Heimdall {
return &Heimdall{sync.Mutex{}, nil, chainConfig, nil, map[uint64]*span.HeimdallSpan{}, logger, nil, nil, libcommon.Address{}, nil}
}
func (h *Heimdall) StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) {
return nil, nil
}
func (h *Heimdall) Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) {
h.Lock()
defer h.Unlock()
if span, ok := h.spans[spanID]; ok {
h.currentSpan = span
return span, nil
}
var nextSpan = span.Span{
ID: spanID,
}
if h.currentSpan == nil || spanID == 0 {
nextSpan.StartBlock = 1 //256
} else {
if spanID != h.currentSpan.ID+1 {
return nil, fmt.Errorf("Can't initialize span: non consecutive span")
}
nextSpan.StartBlock = h.currentSpan.EndBlock + 1
}
nextSpan.EndBlock = nextSpan.StartBlock + (100 * h.chainConfig.Bor.CalculateSprint(nextSpan.StartBlock)) - 1
// TODO we should use a subset here - see: https://wiki.polygon.technology/docs/pos/bor/
selectedProducers := make([]valset.Validator, len(h.validatorSet.Validators))
for i, v := range h.validatorSet.Validators {
selectedProducers[i] = *v
}
h.currentSpan = &span.HeimdallSpan{
Span: nextSpan,
ValidatorSet: *h.validatorSet,
SelectedProducers: selectedProducers,
ChainID: h.chainConfig.ChainID.String(),
}
h.spans[h.currentSpan.ID] = h.currentSpan
return h.currentSpan, nil
}
func (h *Heimdall) currentSprintLength() int {
if h.currentSpan != nil {
return int(h.chainConfig.Bor.CalculateSprint(h.currentSpan.StartBlock))
}
return int(h.chainConfig.Bor.CalculateSprint(256))
}
func (h *Heimdall) FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) {
return nil, fmt.Errorf("TODO")
}
func (h *Heimdall) FetchCheckpointCount(ctx context.Context) (int64, error) {
return 0, fmt.Errorf("TODO")
}
func (h *Heimdall) Close() {
}
func (h *Heimdall) NodeCreated(node devnet.Node) {
h.Lock()
defer h.Unlock()
if strings.HasPrefix(node.Name(), "bor") && node.IsBlockProducer() && node.Account() != nil {
// TODO configurable voting power
h.addValidator(node.Account().Address, 1000, 0)
}
}
func (h *Heimdall) NodeStarted(node devnet.Node) {
if !strings.HasPrefix(node.Name(), "bor") && node.IsBlockProducer() {
h.Lock()
defer h.Unlock()
if h.syncChan != nil {
return
}
h.syncChan = make(chan *contracts.TestStateSenderStateSynced, 100)
go func() {
count, err := node.GetTransactionCount(node.Account().Address, requests.BlockNumbers.Latest)
if err != nil {
h.Lock()
defer h.Unlock()
h.syncChan = nil
h.logger.Error("failed to get transaction count", "address", node.Account().Address.Hex(), "err", err)
return
}
transactOpts, err := bind.NewKeyedTransactorWithChainID(accounts.SigKey(node.Account().Address), node.ChainID())
if err != nil {
h.Lock()
defer h.Unlock()
h.syncChan = nil
h.logger.Error("cannot create transactor ops", "chainId", node.ChainID(), "err", err)
return
}
transactOpts.GasLimit = uint64(200_000)
transactOpts.GasPrice = big.NewInt(880_000_000)
transactOpts.Nonce = count
// deploy the contract and get the contract handler
address, _, contract, err := contracts.DeployTestStateSender(transactOpts, contracts.NewBackend(node))
if err != nil {
h.Lock()
defer h.Unlock()
h.syncChan = nil
h.logger.Error("failed to deploy state sender", "err", err)
return
}
h.logger.Info("StateSender deployed", "addr", address)
h.syncContractAddress = address
h.syncContractBinding = contract
_ /*subscription*/, err = contract.WatchStateSynced(&bind.WatchOpts{}, h.syncChan, nil, nil)
if err != nil {
h.Lock()
defer h.Unlock()
h.syncChan = nil
h.logger.Error("failed to subscribe to sync events", "err", err)
return
}
}()
}
}
func (h *Heimdall) addValidator(validatorAddress libcommon.Address, votingPower int64, proposerPriority int64) {
if h.validatorSet == nil {
h.validatorSet = valset.NewValidatorSet([]*valset.Validator{
{
ID: 1,
Address: validatorAddress,
VotingPower: votingPower,
ProposerPriority: proposerPriority,
},
}, h.logger)
} else {
h.validatorSet.UpdateWithChangeSet([]*valset.Validator{
{
ID: uint64(len(h.validatorSet.Validators) + 1),
Address: validatorAddress,
VotingPower: votingPower,
ProposerPriority: proposerPriority,
},
}, h.logger)
}
}
func (h *Heimdall) Start(ctx context.Context) error {
h.Lock()
if h.cancelFunc != nil {
h.Unlock()
return nil
}
ctx, h.cancelFunc = context.WithCancel(ctx)
h.Unlock()
return heimdallgrpc.StartHeimdallServer(ctx, h, HeimdallGRpc(ctx), h.logger)
}
func HeimdallGRpc(ctx context.Context) string {
addr := "localhost:8540"
if cli := devnet.CliContext(ctx); cli != nil {
if grpcAddr := cli.String("bor.heimdallgRPC"); len(grpcAddr) > 0 {
addr = grpcAddr
}
}
return addr
}
func (h *Heimdall) Stop() {
var cancel context.CancelFunc
h.Lock()
if h.cancelFunc != nil {
cancel = h.cancelFunc
h.cancelFunc = nil
}
h.Unlock()
if cancel != nil {
cancel()
}
}