From 59909a7efe96db92fb97e200285bc962031059e9 Mon Sep 17 00:00:00 2001 From: Pratik Patil Date: Fri, 24 Nov 2023 15:56:33 +0530 Subject: [PATCH] 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. --- consensus/bor/bor.go | 112 +++++++++++++++++++++++---- consensus/bor/snapshot.go | 2 +- core/types/block.go | 6 +- core/types/block_test.go | 81 +++++++++++++++++++ erigon-lib/chain/chain_config.go | 13 ++++ eth/stagedsync/stage_bor_heimdall.go | 5 +- 6 files changed, 200 insertions(+), 19 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 507119845..fe913ed77 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -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 +} diff --git a/consensus/bor/snapshot.go b/consensus/bor/snapshot.go index d28d76c7c..836acf363 100644 --- a/consensus/bor/snapshot.go +++ b/consensus/bor/snapshot.go @@ -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) diff --git a/core/types/block.go b/core/types/block.go index af0256833..1a434098b 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -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 diff --git a/core/types/block_test.go b/core/types/block_test.go index 3b62f66b1..4e2a8303d 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -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() diff --git a/erigon-lib/chain/chain_config.go b/erigon-lib/chain/chain_config.go index 78d8da8e6..6e93c59ff 100644 --- a/erigon-lib/chain/chain_config.go +++ b/erigon-lib/chain/chain_config.go @@ -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) } diff --git a/eth/stagedsync/stage_bor_heimdall.go b/eth/stagedsync/stage_bor_heimdall.go index 46b7507d3..d990fc952 100644 --- a/eth/stagedsync/stage_bor_heimdall.go +++ b/eth/stagedsync/stage_bor_heimdall.go @@ -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 }