package bor_test import ( "context" "encoding/json" "fmt" "math/big" "testing" "github.com/ledgerwatch/erigon-lib/chain" "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry" "github.com/ledgerwatch/erigon-lib/kv/memdb" "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/bor" "github.com/ledgerwatch/erigon/consensus/bor/clerk" "github.com/ledgerwatch/erigon/consensus/bor/contract" "github.com/ledgerwatch/erigon/consensus/bor/heimdall/checkpoint" "github.com/ledgerwatch/erigon/consensus/bor/heimdall/milestone" "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/eth/protocols/eth" "github.com/ledgerwatch/erigon/ethdb/prune" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/turbo/stages/mock" "github.com/ledgerwatch/log/v3" ) type test_heimdall struct { currentSpan *span.HeimdallSpan chainConfig *chain.Config validatorSet *valset.ValidatorSet spans map[uint64]*span.HeimdallSpan } func newTestHeimdall(chainConfig *chain.Config) *test_heimdall { return &test_heimdall{nil, chainConfig, nil, map[uint64]*span.HeimdallSpan{}} } func (h test_heimdall) StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) { return nil, nil } func (h *test_heimdall) Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) { 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 test_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 test_heimdall) FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) { return nil, fmt.Errorf("TODO") } func (h test_heimdall) FetchCheckpointCount(ctx context.Context) (int64, error) { return 0, fmt.Errorf("TODO") } func (h test_heimdall) FetchMilestone(ctx context.Context) (*milestone.Milestone, error) { return nil, fmt.Errorf("TODO") } func (h test_heimdall) FetchMilestoneCount(ctx context.Context) (int64, error) { return 0, fmt.Errorf("TODO") } func (h test_heimdall) FetchNoAckMilestone(ctx context.Context, milestoneID string) error { return fmt.Errorf("TODO") } func (h test_heimdall) FetchLastNoAckMilestone(ctx context.Context) (string, error) { return "", fmt.Errorf("TODO") } func (h test_heimdall) FetchMilestoneID(ctx context.Context, milestoneID string) error { return fmt.Errorf("TODO") } func (h test_heimdall) Close() {} type test_genesisContract struct { } func (g test_genesisContract) CommitState(event rlp.RawValue, syscall consensus.SystemCall) error { return nil } func (g test_genesisContract) LastStateId(syscall consensus.SystemCall) (*big.Int, error) { return big.NewInt(0), nil } type headerReader struct { validator validator } func (r headerReader) Config() *chain.Config { return r.validator.ChainConfig } func (r headerReader) FrozenBlocks() uint64 { return 0 } func (r headerReader) CurrentHeader() *types.Header { return nil } func (r headerReader) GetHeader(_ libcommon.Hash, blockNo uint64) *types.Header { return r.GetHeaderByNumber(blockNo) } func (r headerReader) GetHeaderByNumber(blockNo uint64) *types.Header { if r.validator.blocks != nil { if block, ok := r.validator.blocks[blockNo]; ok { return block.Header() } } return nil } func (r headerReader) GetHeaderByHash(libcommon.Hash) *types.Header { return nil } func (r headerReader) GetTd(libcommon.Hash, uint64) *big.Int { return nil } func (r headerReader) BorSpan(spanId uint64) []byte { b, _ := json.Marshal(&r.validator.heimdall.currentSpan) return b } type spanner struct { *span.ChainSpanner validatorAddress common.Address currentSpan span.Span } func (c spanner) GetCurrentSpan(_ consensus.SystemCall) (*span.Span, error) { return &c.currentSpan, nil } func (c *spanner) CommitSpan(heimdallSpan span.HeimdallSpan, syscall consensus.SystemCall) error { c.currentSpan = heimdallSpan.Span return nil } func (c *spanner) GetCurrentValidators(spanId uint64, signer libcommon.Address, chain consensus.ChainHeaderReader) ([]*valset.Validator, error) { return []*valset.Validator{ { ID: 1, Address: c.validatorAddress, VotingPower: 1000, ProposerPriority: 1, }}, nil } type validator struct { *mock.MockSentry heimdall *test_heimdall blocks map[uint64]*types.Block } func (v validator) generateChain(length int) (*core.ChainPack, error) { return core.GenerateChain(v.ChainConfig, v.Genesis, v.Engine, v.DB, length, func(i int, block *core.BlockGen) { v.blocks[block.GetParent().NumberU64()] = block.GetParent() }) } func (v validator) IsProposer(block *types.Block) (bool, error) { return v.Engine.(*bor.Bor).IsProposer(block.Header()) } func (v validator) sealBlocks(blocks []*types.Block) ([]*types.Block, error) { sealedBlocks := make([]*types.Block, 0, len(blocks)) hr := headerReader{v} for _, block := range blocks { header := block.HeaderNoCopy() if err := v.Engine.Prepare(hr, header, nil); err != nil { return nil, err } if parent := hr.GetHeaderByNumber(header.Number.Uint64() - 1); parent != nil { header.ParentHash = parent.Hash() } sealResults := make(chan *types.Block, 1) if err := v.Engine.Seal(hr, block, sealResults, nil); err != nil { return nil, err } sealedBlock := <-sealResults v.blocks[sealedBlock.NumberU64()] = sealedBlock sealedBlocks = append(sealedBlocks, sealedBlock) } return sealedBlocks, nil } func (v validator) verifyBlocks(blocks []*types.Block) error { hr := headerReader{v} for i, block := range blocks { if err := v.Engine.VerifyHeader(hr, block.Header(), false); err != nil { return fmt.Errorf("block %d: failed to verify: %w", i, err) } } return nil } func newValidator(t *testing.T, heimdall *test_heimdall, blocks map[uint64]*types.Block) validator { logger := log.Root() validatorKey, _ := crypto.GenerateKey() validatorAddress := crypto.PubkeyToAddress(validatorKey.PublicKey) bor := bor.New( heimdall.chainConfig, memdb.New(""), nil, /* blockReader */ &spanner{span.NewChainSpanner(contract.ValidatorSet(), heimdall.chainConfig, false, logger), validatorAddress, span.Span{}}, heimdall, test_genesisContract{}, logger, ) /*fmt.Printf("Private: 0x%s\nPublic: 0x%s\nAddress: %s\n", hex.EncodeToString(crypto.FromECDSA(validatorKey)), hex.EncodeToString(crypto.MarshalPubkey(&validatorKey.PublicKey)), strings.ToLower(validatorAddress.Hex()))*/ if heimdall.validatorSet == nil { heimdall.validatorSet = valset.NewValidatorSet([]*valset.Validator{ { ID: 1, Address: validatorAddress, VotingPower: 1000, ProposerPriority: 1, }, }, logger) } else { heimdall.validatorSet.UpdateWithChangeSet([]*valset.Validator{ { ID: uint64(len(heimdall.validatorSet.Validators) + 1), Address: validatorAddress, VotingPower: 1000, ProposerPriority: 1, }, }, logger) } bor.Authorize(validatorAddress, func(_ libcommon.Address, mimeType string, message []byte) ([]byte, error) { return crypto.Sign(crypto.Keccak256(message), validatorKey) }) checkStateRoot := true return validator{ mock.MockWithEverything(t, &types.Genesis{Config: heimdall.chainConfig}, validatorKey, prune.DefaultMode, bor, 1024, false, false, checkStateRoot), heimdall, blocks, } } func TestValidatorCreate(t *testing.T) { newValidator(t, newTestHeimdall(params.BorDevnetChainConfig), map[uint64]*types.Block{}) } func TestVerifyHeader(t *testing.T) { v := newValidator(t, newTestHeimdall(params.BorDevnetChainConfig), map[uint64]*types.Block{}) chain, err := v.generateChain(1) if err != nil { t.Fatalf("generate blocks failed: %v", err) } sealedBlocks, err := v.sealBlocks(chain.Blocks) if err != nil { t.Fatalf("seal block failed: %v", err) } err = v.verifyBlocks(sealedBlocks) if err != nil { t.Fatalf("verify blocks failed: %v", err) } } func TestVerifyRun(t *testing.T) { //testVerify(t, 5, 8) } func TestVerifySprint(t *testing.T) { //testVerify(t, 10, 4, int(params.BorDevnetChainConfig.Bor.CalculateSprint(256))) } func TestVerifySpan(t *testing.T) { //testVerify(t, 10, 4 /*100**/ *int(params.BorDevnetChainConfig.Bor.CalculateSprint(256))) } func testVerify(t *testing.T, noValidators int, chainLength int) { log.Root().SetHandler(log.StderrHandler) heimdall := newTestHeimdall(params.BorDevnetChainConfig) blocks := map[uint64]*types.Block{} validators := make([]validator, noValidators) for i := 0; i < noValidators; i++ { validators[i] = newValidator(t, heimdall, blocks) } chains := make([]*core.ChainPack, noValidators) for i, v := range validators { chain, err := v.generateChain(chainLength) if err != nil { t.Fatalf("generate blocks failed: %v", err) } chains[i] = chain } lastProposerIndex := -1 for bi := 0; bi < chainLength; bi++ { for vi, v := range validators { block := chains[vi].Blocks[bi] isProposer, err := v.IsProposer(block) if err != nil { t.Fatal(err) } if isProposer { if vi != lastProposerIndex { sprintLen := params.BorDevnetChainConfig.Bor.CalculateSprint(block.NumberU64()) if block.NumberU64() > 1 && block.NumberU64()%sprintLen != 0 { t.Fatalf("Unexpected sprint boundary at %d for: %d", bi, block.NumberU64()) } lastProposerIndex = vi } sealedBlocks, err := v.sealBlocks([]*types.Block{block}) if err != nil { t.Fatalf("seal block failed: %v", err) } err = v.verifyBlocks(sealedBlocks) if err != nil { t.Fatalf("verify blocks failed: %v", err) } } } } } func TestSendBlock(t *testing.T) { heimdall := newTestHeimdall(params.BorDevnetChainConfig) blocks := map[uint64]*types.Block{} s := newValidator(t, heimdall, blocks) r := newValidator(t, heimdall, blocks) chain, err := s.generateChain(1) if err != nil { t.Fatalf("generate blocks failed: %v", err) } sealedBlocks, err := s.sealBlocks(chain.Blocks) if err != nil { t.Fatalf("seal block failed: %v", err) } err = s.verifyBlocks(sealedBlocks) if err != nil { t.Fatalf("verify blocks failed: %v", err) } b, err := rlp.EncodeToBytes(ð.NewBlockPacket{ Block: sealedBlocks[0], TD: big.NewInt(1), // This is ignored anyway }) if err != nil { t.Fatal(err) } r.ReceiveWg.Add(1) for _, err = range r.Send(&sentry.InboundMessage{Id: sentry.MessageId_NEW_BLOCK_66, Data: b, PeerId: s.PeerId}) { if err != nil { t.Fatal(err) } } r.ReceiveWg.Wait() } /* if err = m.InsertChain(longerChain, nil); err != nil { t.Fatal(err) } */