Skip to content

Commit

Permalink
Added TxDependency Metadata to ExtraData in Block Header in Bor for B…
Browse files Browse the repository at this point in the history
…lock-STM (#8037)

This PR adds support to store the transaction dependency (generated by
the block producer) in the block header for bor. This transaction
dependency will then be used by the parallel processor
([Block-STM](#7812)).

I have created another
[PR](ledgerwatch/erigon-lib#1064) in the
erigon-lib repo which adds the `IsParallelUniverse()` function.
  • Loading branch information
pratikspatil024 authored Nov 24, 2023
1 parent 28fff1b commit 59909a7
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 19 deletions.
112 changes: 97 additions & 15 deletions consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ var (
"0": 64,
} // Default number of blocks after which to checkpoint and reset the pending votes

extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal

uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.

// diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
Expand Down Expand Up @@ -154,11 +151,11 @@ func Ecrecover(header *types.Header, sigcache *lru.ARCCache[libcommon.Hash, libc
return address, nil
}
// Retrieve the signature from the header extra-data
if len(header.Extra) < extraSeal {
if len(header.Extra) < types.ExtraSealLength {
return libcommon.Address{}, errMissingSignature
}

signature := header.Extra[len(header.Extra)-extraSeal:]
signature := header.Extra[len(header.Extra)-types.ExtraSealLength:]

// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(SealHash(header, c).Bytes(), signature)
Expand Down Expand Up @@ -542,7 +539,7 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head
isSprintEnd := isSprintStart(number+1, c.config.CalculateSprint(number))

// Ensure that the extra-data contains a signer list on checkpoint, but none otherwise
signersBytes := len(header.Extra) - extraVanity - extraSeal
signersBytes := len(GetValidatorBytes(header, c.config))
if !isSprintEnd && signersBytes != 0 {
return errExtraValidators
}
Expand Down Expand Up @@ -584,11 +581,11 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head
// ValidateHeaderExtraField validates that the extra-data contains both the vanity and signature.
// header.Extra = header.Vanity + header.ProducerBytes (optional) + header.Seal
func ValidateHeaderExtraField(extraBytes []byte) error {
if len(extraBytes) < extraVanity {
if len(extraBytes) < types.ExtraVanityLength {
return errMissingVanity
}

if len(extraBytes) < extraVanity+extraSeal {
if len(extraBytes) < types.ExtraVanityLength+types.ExtraSealLength {
return errMissingSignature
}

Expand Down Expand Up @@ -917,11 +914,11 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s
header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.authorizedSigner.Load().signer))

// Ensure the extra data has all it's components
if len(header.Extra) < extraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
if len(header.Extra) < types.ExtraVanityLength {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, types.ExtraVanityLength-len(header.Extra))...)
}

header.Extra = header.Extra[:extraVanity]
header.Extra = header.Extra[:types.ExtraVanityLength]

// get validator set if number
// Note: headers.Extra has producer set and not validator set. The bor
Expand All @@ -941,13 +938,47 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s
// sort validator by address
sort.Sort(valset.ValidatorsByAddress(newValidators))

for _, validator := range newValidators {
header.Extra = append(header.Extra, validator.HeaderBytes()...)
if c.config.IsParallelUniverse(header.Number.Uint64()) {
var tempValidatorBytes []byte

for _, validator := range newValidators {
tempValidatorBytes = append(tempValidatorBytes, validator.HeaderBytes()...)
}

blockExtraData := &BlockExtraData{
ValidatorBytes: tempValidatorBytes,
TxDependency: nil,
}

blockExtraDataBytes, err := rlp.EncodeToBytes(blockExtraData)
if err != nil {
log.Error("error while encoding block extra data: %v", err)
return fmt.Errorf("error while encoding block extra data: %v", err)
}

header.Extra = append(header.Extra, blockExtraDataBytes...)
} else {
for _, validator := range newValidators {
header.Extra = append(header.Extra, validator.HeaderBytes()...)
}
}
} else if c.config.IsParallelUniverse(header.Number.Uint64()) {
blockExtraData := &BlockExtraData{
ValidatorBytes: nil,
TxDependency: nil,
}

blockExtraDataBytes, err := rlp.EncodeToBytes(blockExtraData)
if err != nil {
log.Error("error while encoding block extra data: %v", err)
return fmt.Errorf("error while encoding block extra data: %v", err)
}

header.Extra = append(header.Extra, blockExtraDataBytes...)
}

// add extra seal space
header.Extra = append(header.Extra, make([]byte, extraSeal)...)
header.Extra = append(header.Extra, make([]byte, types.ExtraSealLength)...)

// Mix digest is reserved for now, set to empty
header.MixDigest = libcommon.Hash{}
Expand Down Expand Up @@ -1161,7 +1192,7 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result
if err != nil {
return err
}
copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
copy(header.Extra[len(header.Extra)-types.ExtraSealLength:], sighash)

go func() {
// Wait until sealing is terminated or delay timeout.
Expand Down Expand Up @@ -1576,3 +1607,54 @@ func getUpdatedValidatorSet(oldValidatorSet *valset.ValidatorSet, newVals []*val
func isSprintStart(number, sprint uint64) bool {
return number%sprint == 0
}

// In bor, RLP encoding of BlockExtraData will be stored in the Extra field in the header
type BlockExtraData struct {
// Validator bytes of bor
ValidatorBytes []byte

// length of TxDependency -> n (n = number of transactions in the block)
// length of TxDependency[i] -> k (k = a whole number)
// k elements in TxDependency[i] -> transaction indexes on which transaction i is dependent on
TxDependency [][]uint64
}

// Returns the Block-STM Transaction Dependency from the block header
func GetTxDependency(b *types.Block) [][]uint64 {
tempExtra := b.Extra()

if len(tempExtra) < types.ExtraVanityLength+types.ExtraSealLength {
log.Error("length of extra less is than vanity and seal")
return nil
}

var blockExtraData BlockExtraData

if err := rlp.DecodeBytes(tempExtra[types.ExtraVanityLength:len(tempExtra)-types.ExtraSealLength], &blockExtraData); err != nil {
log.Error("error while decoding block extra data", "err", err)
return nil
}

return blockExtraData.TxDependency
}

func GetValidatorBytes(h *types.Header, config *chain.BorConfig) []byte {
tempExtra := h.Extra

if !config.IsParallelUniverse(h.Number.Uint64()) {
return tempExtra[types.ExtraVanityLength : len(tempExtra)-types.ExtraSealLength]
}

if len(tempExtra) < types.ExtraVanityLength+types.ExtraSealLength {
log.Error("length of extra less is than vanity and seal")
return nil
}

var blockExtraData BlockExtraData
if err := rlp.DecodeBytes(tempExtra[types.ExtraVanityLength:len(tempExtra)-types.ExtraSealLength], &blockExtraData); err != nil {
log.Error("error while decoding block extra data", "err", err)
return nil
}

return blockExtraData.ValidatorBytes
}
2 changes: 1 addition & 1 deletion consensus/bor/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (s *Snapshot) Apply(parent *types.Header, headers []*types.Header, logger l
if err := ValidateHeaderExtraField(header.Extra); err != nil {
return snap, err
}
validatorBytes := header.Extra[extraVanity : len(header.Extra)-extraSeal]
validatorBytes := GetValidatorBytes(header, s.config)

// get validators from headers and use that for new validator set
newVals, _ := valset.ParseValidators(validatorBytes)
Expand Down
6 changes: 5 additions & 1 deletion core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/ledgerwatch/erigon-lib/common/hexutil"
"io"
"math/big"
"reflect"
"sync/atomic"

"github.com/ledgerwatch/erigon-lib/common/hexutil"

"github.com/gballet/go-verkle"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
Expand All @@ -40,6 +41,9 @@ import (
var (
EmptyRootHash = libcommon.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
EmptyUncleHash = rlpHash([]*Header(nil))

ExtraVanityLength = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
ExtraSealLength = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
)

// A BlockNonce is a 64-bit hash which proves (combined with the
Expand Down
81 changes: 81 additions & 0 deletions core/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
types2 "github.com/ledgerwatch/erigon-lib/types"
"github.com/ledgerwatch/log/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -39,6 +40,86 @@ import (
"github.com/ledgerwatch/erigon/rlp"
)

// the following 2 functions are replica for the test
// This is a replica of `bor.GetValidatorBytes` function
// This was needed because currently, `IsParallelUniverse` will always return false.
func GetValidatorBytesTest(h *Header) []byte {
if len(h.Extra) < ExtraVanityLength+ExtraSealLength {
log.Error("length of extra is less than vanity and seal")
return nil
}

var blockExtraData BlockExtraDataTest
if err := rlp.DecodeBytes(h.Extra[ExtraVanityLength:len(h.Extra)-ExtraSealLength], &blockExtraData); err != nil {
log.Error("error while decoding block extra data", "err", err)
return nil
}

return blockExtraData.ValidatorBytes
}

func GetTxDependencyTest(b *Block) [][]uint64 {
if len(b.header.Extra) < ExtraVanityLength+ExtraSealLength {
log.Error("length of extra less is than vanity and seal")
return nil
}

var blockExtraData BlockExtraDataTest
if err := rlp.DecodeBytes(b.header.Extra[ExtraVanityLength:len(b.header.Extra)-ExtraSealLength], &blockExtraData); err != nil {
log.Error("error while decoding block extra data", "err", err)
return nil
}

return blockExtraData.TxDependency
}

type BlockExtraDataTest struct {
// Validator bytes of bor
ValidatorBytes []byte

// length of TxDependency -> n (n = number of transactions in the block)
// length of TxDependency[i] -> k (k = a whole number)
// k elements in TxDependency[i] -> transaction indexes on which transaction i is dependent on
TxDependency [][]uint64
}

func TestTxDependencyBlockDecoding(t *testing.T) {
t.Parallel()

blockEnc := common.FromHex("f90270f9026ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000080832fefd8825208845506eb07b8710000000000000000000000000000000000000000000000000000000000000000cf8776616c20736574c6c20201c201800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498880000000000000000c0c0")

var block Block

if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
t.Fatal("decode error: ", err)
}
check := func(f string, got, want interface{}) {
if !reflect.DeepEqual(got, want) {
t.Errorf("%s mismatch: got %v, want %v", f, got, want)
}
}

check("Coinbase", block.Coinbase(), libcommon.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
check("MixDigest", block.MixDigest(), libcommon.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
check("Root", block.Root(), libcommon.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
check("Time", block.Time(), uint64(1426516743))

validatorBytes := GetValidatorBytesTest(block.header)
txDependency := GetTxDependencyTest(&block)

check("validatorBytes", validatorBytes, []byte("val set"))
check("txDependency", txDependency, [][]uint64{{2, 1}, {1, 0}})

ourBlockEnc, err := rlp.EncodeToBytes(&block)

if err != nil {
t.Fatal("encode error: ", err)
}
if !bytes.Equal(ourBlockEnc, blockEnc) {
t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc)
}
}

// from bcValidBlockTest.json, "SimpleTx"
func TestBlockEncoding(t *testing.T) {
t.Parallel()
Expand Down
13 changes: 13 additions & 0 deletions erigon-lib/chain/chain_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ type BorConfig struct {
AgraBlock *big.Int `json:"agraBlock"` // Agra switch block (nil = no fork, 0 = already in agra)
StateSyncConfirmationDelay map[string]uint64 `json:"stateSyncConfirmationDelay"` // StateSync Confirmation Delay, in seconds, to calculate `to`

ParallelUniverseBlock *big.Int `json:"parallelUniverseBlock"` // TODO: update all occurrence, change name and finalize number (hardfork for block-stm related changes)

sprints sprints
}

Expand Down Expand Up @@ -561,6 +563,17 @@ func (c *BorConfig) IsIndore(number uint64) bool {
return isForked(c.IndoreBlock, number)
}

// TODO: modify this function once the block number is finalized
func (c *BorConfig) IsParallelUniverse(number uint64) bool {
if c.ParallelUniverseBlock != nil {
if c.ParallelUniverseBlock.Cmp(big.NewInt(0)) == 0 {
return false
}
}

return isForked(c.ParallelUniverseBlock, number)
}

func (c *BorConfig) CalculateStateSyncDelay(number uint64) uint64 {
return borKeyValueConfigHelper(c.StateSyncConfirmationDelay, number)
}
Expand Down
5 changes: 3 additions & 2 deletions eth/stagedsync/stage_bor_heimdall.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func BorHeimdallForward(
if !mine && header != nil {
sprintLength := cfg.chainConfig.Bor.CalculateSprint(blockNum)
if blockNum > zerothSpanEnd && ((blockNum+1)%sprintLength == 0) {
if err = checkHeaderExtraData(u, ctx, chain, blockNum, header); err != nil {
if err = checkHeaderExtraData(u, ctx, chain, blockNum, header, cfg.chainConfig.Bor); err != nil {
return err
}
}
Expand Down Expand Up @@ -322,6 +322,7 @@ func checkHeaderExtraData(
chain consensus.ChainHeaderReader,
blockNum uint64,
header *types.Header,
config *chain.BorConfig,
) error {
var spanID uint64
if blockNum+1 > zerothSpanEnd {
Expand All @@ -339,7 +340,7 @@ func checkHeaderExtraData(

sort.Sort(valset.ValidatorsByAddress(producerSet))

headerVals, err := valset.ParseValidators(header.Extra[extraVanity : len(header.Extra)-extraSeal])
headerVals, err := valset.ParseValidators(bor.GetValidatorBytes(header, config))
if err != nil {
return err
}
Expand Down

0 comments on commit 59909a7

Please sign in to comment.