Added TxDependency Metadata to ExtraData in Block Header in Bor for Block-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](https://github.com/ledgerwatch/erigon/pull/7812/)).

I have created another
[PR](https://github.com/ledgerwatch/erigon-lib/pull/1064) in the
erigon-lib repo which adds the `IsParallelUniverse()` function.
This commit is contained in:
Pratik Patil 2023-11-24 15:56:33 +05:30 committed by GitHub
parent 28fff1b35e
commit 59909a7efe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 200 additions and 19 deletions

View File

@ -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
@ -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)
@ -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
}
@ -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
}
@ -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
@ -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{}
@ -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.
@ -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
}

View File

@ -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)

View File

@ -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"
@ -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

View File

@ -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"
@ -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()

View File

@ -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
}
@ -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)
}

View File

@ -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
}
}
@ -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 {
@ -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
}