From 18f0e5beeead4337db12f6a01ac42dcf0a782006 Mon Sep 17 00:00:00 2001 From: minh-bq <97180373+minh-bq@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:01:17 +0700 Subject: [PATCH] consortium-v2: make the period definition consistent with contract (#638) * consortium-v2: make the period definition consistent with contract Currently, Ronin detects the start of new period is the first block of an epoch where that block's timestamp is on the different date from first block of previous epoch. However, in contract, the start of new period is the first block of epoch where the parent of it (the last block of previous epoch)'s timestamp is on the different date from the last block of "previous previous" epoch. In this commit, we change the Ronin logic to be consistent with contract after Venoki hardfork. * consortium-v2: remove Tripp hardfork check in IsPeriodBlock IsPeriodBlock is only called when passing Tripp hardfork already so we don't need to check it further inside the function. --- consensus/consortium/v2/consortium.go | 101 +++++-- consensus/consortium/v2/consortium_test.go | 319 ++++++++++++++++++--- consensus/consortium/v2/snapshot.go | 20 +- 3 files changed, 384 insertions(+), 56 deletions(-) diff --git a/consensus/consortium/v2/consortium.go b/consensus/consortium/v2/consortium.go index cd7682c0d..071d9dfcc 100644 --- a/consensus/consortium/v2/consortium.go +++ b/consensus/consortium/v2/consortium.go @@ -475,7 +475,12 @@ func (c *Consortium) verifyValidatorFieldsInExtraData( extraData.BlockProducersBitSet, ) } - if c.IsPeriodBlock(chain, header, parents) { + isPeriodBlock, err := c.IsPeriodBlock(chain, header, parents) + if err != nil { + log.Error("Failed to check IsPeriodBlock", "blocknum", header.Number, "err", err) + return err + } + if isPeriodBlock { if len(extraData.CheckpointValidators) == 0 { return fmt.Errorf( "%w: checkpoint validator: %v", @@ -679,13 +684,18 @@ func (c *Consortium) snapshot(chain consensus.ChainHeaderReader, number uint64, break } - // this case is only happened in mock mode + // this case is only happened in mock/test mode if number == 0 { validators, err := c.contract.GetBlockProducers(common.Hash{}, common.Big0) if err != nil { return nil, err } snap = newSnapshot(c.chainConfig, c.config, c.signatures, number, hash, validators, nil, c.ethAPI) + if c.db != nil { + if err := snap.store(c.db); err != nil { + return nil, err + } + } break } @@ -931,7 +941,13 @@ func (c *Consortium) getCheckpointValidatorsFromContract( if !isAaron { sort.Sort(validatorsAscending(blockProducers)) } - if !c.IsPeriodBlock(chain, header, nil) { + + isPeriodBlock, err := c.IsPeriodBlock(chain, header, nil) + if err != nil { + log.Error("Failed to check IsPeriodBlock", "blocknum", header.Number, "err", err) + return nil, nil, err + } + if !isPeriodBlock { return nil, blockProducers, nil } validatorCandidates, err := contract.GetValidatorCandidates(parentHash, parentBlockNumber) @@ -1048,7 +1064,12 @@ func (c *Consortium) Prepare(chain consensus.ChainHeaderReader, header *types.He // current epoch, which is used to calculate block producer bit set later on. var latestValidatorCandidates []finality.ValidatorWithBlsPub - if c.IsPeriodBlock(chain, header, nil) { + isPeriodBlock, err := c.IsPeriodBlock(chain, header, nil) + if err != nil { + log.Error("Failed to check IsPeriodBlock", "blocknum", header.Number, "err", err) + return err + } + if isPeriodBlock { extraData.CheckpointValidators = checkpointValidators latestValidatorCandidates = checkpointValidators } else { @@ -1278,8 +1299,13 @@ func (c *Consortium) Finalize(chain consensus.ChainHeaderReader, header *types.H // If isTripp and new period, read all validator candidates and // their amounts, check with stored data in header if c.IsTrippEffective(chain, header) { + isPeriodBlock, err := c.IsPeriodBlock(chain, header, nil) + if err != nil { + log.Error("Failed to check IsPeriodBlock", "blocknum", header.Number, "err", err) + return err + } if c.chainConfig.IsAaron(header.Number) { - if !c.IsPeriodBlock(chain, header, nil) { + if !isPeriodBlock { // Except period block, checkpoint validator list get from contract // is nil at other epoch blocks. From the fact that validator candidate list // does not change over the whole period, it's possible to get the latest @@ -1303,7 +1329,7 @@ func (c *Consortium) Finalize(chain consensus.ChainHeaderReader, header *types.H } } } - if c.IsPeriodBlock(chain, header, nil) { + if isPeriodBlock { if err := verifyValidatorExtraDataWithContract(checkpointValidators, extraData, true, true); err != nil { return err } @@ -1937,29 +1963,64 @@ func (c *Consortium) getLastCheckpointHeader(chain consensus.ChainHeaderReader, return current } +func getParentHeader(chain consensus.ChainHeaderReader, currentHeader *types.Header, parents []*types.Header) *types.Header { + if len(parents) > 0 { + return parents[len(parents)-1] + } else { + return chain.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) + } +} + // IsPeriodBlock returns indicator whether a block is a period checkpoint block or not, // which is the first checkpoint block (block % EpochV2 == 0) after 00:00 UTC everyday. -func (c *Consortium) IsPeriodBlock(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) bool { +// +// Before Venoki, IsPeriodBlock returns true when header is the first block of an epoch +// whose timestamp is on the different date from first block of previous epoch. +// After Venoki, IsPeriodBlock returns true when header is the first block of an epoch +// (let's call epoch n) whose parent block's timestamp is on the different date from the +// last block of "previous previous" epoch (epoch n - 2) +// +// The caller must ensure to call this function after passing Tripp hardfork only. +func (c *Consortium) IsPeriodBlock(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) (bool, error) { if c.isTest { - return c.testTrippPeriod + return c.testTrippPeriod, nil } number := header.Number.Uint64() - if number%c.config.EpochV2 != 0 || !chain.Config().IsTripp(header.Number) { - return false + if number%c.config.EpochV2 != 0 { + return false, nil } - // Derive parent snapshot. If err, we recursively find the nearest epoch - // block, and determine whether the header period is ahead of that block period. - snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) - if err != nil { - log.Warn("Fail to get snapshot at", "blockNumber", number-1, "blockHash", header.ParentHash, "err", err) - parent := c.getLastCheckpointHeader(chain, header) - if parent == nil { - return false + // When transitioning to Venoki, we actually check the last block of previous epoch + // with the first block of previous epoch. This is unusual but the overall behavior + // shows no difference. Furthermore, we will set the Venoki in the middle of period so + // when transitioning, it is guaranteed to have no change in the period number. As a + // result, we can simplify the code to check Venoki transition here. + if chain.Config().IsVenoki(header.Number) { + snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + log.Error("Failed to get snapshot", "number", number-1, "hash", header.ParentHash) + return false, err + } + parentHeader := getParentHeader(chain, header, parents) + if parentHeader == nil { + log.Error("Failed to get parent header", "number", number-1, "hash", header.ParentHash) + return false, consensus.ErrUnknownAncestor + } + return uint64(parentHeader.Time/dayInSeconds) > snap.CurrentPeriod, nil + } else { + // Derive parent snapshot. If err, we recursively find the nearest epoch + // block, and determine whether the header period is ahead of that block period. + snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + log.Warn("Fail to get snapshot at", "blockNumber", number-1, "blockHash", header.ParentHash, "err", err) + parent := c.getLastCheckpointHeader(chain, header) + if parent == nil { + return false, consensus.ErrUnknownAncestor + } + return uint64(header.Time/dayInSeconds) > uint64(parent.Time/dayInSeconds), nil } - return uint64(header.Time/dayInSeconds) > uint64(parent.Time/dayInSeconds) + return uint64(header.Time/dayInSeconds) > snap.CurrentPeriod, nil } - return uint64(header.Time/dayInSeconds) > snap.CurrentPeriod } // IsTrippEffective returns indicator whether the Tripp consensus rule is effective, diff --git a/consensus/consortium/v2/consortium_test.go b/consensus/consortium/v2/consortium_test.go index 197b6f94a..1f580f917 100644 --- a/consensus/consortium/v2/consortium_test.go +++ b/consensus/consortium/v2/consortium_test.go @@ -18,6 +18,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" gokzg4844 "github.com/crate-crypto/go-kzg-4844" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" consortiumCommon "github.com/ethereum/go-ethereum/consensus/consortium/common" "github.com/ethereum/go-ethereum/consensus/consortium/v2/finality" @@ -30,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/bls/blst" blsCommon "github.com/ethereum/go-ethereum/crypto/bls/common" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/hashicorp/golang-lru/arc/v2" @@ -1109,8 +1111,14 @@ func TestGetCheckpointValidatorFromContract(t *testing.T) { } } +type mockValidator struct { + blsPubKey blsCommon.PublicKey + stakedAmount *big.Int +} + type mockContract struct { - validators map[common.Address]blsCommon.PublicKey + validators map[common.Address]mockValidator + maxValidatorNumber *big.Int } func (contract *mockContract) WrapUpEpoch(opts *consortiumCommon.ApplyTransactOpts) error { @@ -1150,13 +1158,17 @@ func (contract *mockContract) GetBlockProducers(_ common.Hash, _ *big.Int) ([]co } func (contract *mockContract) GetValidatorCandidates(_ common.Hash, _ *big.Int) ([]common.Address, error) { - return nil, nil + var validatorAddresses []common.Address + for address := range contract.validators { + validatorAddresses = append(validatorAddresses, address) + } + return validatorAddresses, nil } func (contract *mockContract) GetBlsPublicKey(_ common.Hash, _ *big.Int, address common.Address) (blsCommon.PublicKey, error) { - if key, ok := contract.validators[address]; ok { - if key != nil { - return key, nil + if val, ok := contract.validators[address]; ok { + if val.blsPubKey != nil { + return val.blsPubKey, nil } else { return nil, errors.New("no BLS public key found") } @@ -1165,12 +1177,16 @@ func (contract *mockContract) GetBlsPublicKey(_ common.Hash, _ *big.Int, address } } -func (contract *mockContract) GetStakedAmount(_ common.Hash, _ *big.Int, _ []common.Address) ([]*big.Int, error) { - return nil, nil +func (contract *mockContract) GetStakedAmount(_ common.Hash, _ *big.Int, candidates []common.Address) ([]*big.Int, error) { + stakedAmount := make([]*big.Int, len(candidates)) + for i, candidate := range candidates { + stakedAmount[i] = contract.validators[candidate].stakedAmount + } + return stakedAmount, nil } func (contract *mockContract) GetMaxValidatorNumber(blockHash common.Hash, blockNumber *big.Int) (*big.Int, error) { - return nil, nil + return contract.maxValidatorNumber, nil } type mockVotePool struct { @@ -1573,9 +1589,11 @@ func TestKnownBlockReorg(t *testing.T) { }).MustCommit(db) mock := &mockContract{ - validators: make(map[common.Address]blsCommon.PublicKey), + validators: make(map[common.Address]mockValidator), + } + mock.validators[validatorAddrs[0]] = mockValidator{ + blsPubKey: blsKeys[0].PublicKey(), } - mock.validators[validatorAddrs[0]] = blsKeys[0].PublicKey() recents, _ := arc.NewARC[common.Hash, *Snapshot](inmemorySnapshots) signatures, _ := arc.NewARC[common.Hash, common.Address](inmemorySignatures) @@ -1621,7 +1639,9 @@ func TestKnownBlockReorg(t *testing.T) { } for i := range validatorAddrs { - mock.validators[validatorAddrs[i]] = blsKeys[i].PublicKey() + mock.validators[validatorAddrs[i]] = mockValidator{ + blsPubKey: blsKeys[i].PublicKey(), + } } var checkpointValidators []finality.ValidatorWithBlsPub @@ -1824,8 +1844,10 @@ func TestUpgradeRoninTrustedOrg(t *testing.T) { }).MustCommit(db) mock := &mockContract{ - validators: map[common.Address]blsCommon.PublicKey{ - validatorAddr: blsSecretKey.PublicKey(), + validators: map[common.Address]mockValidator{ + validatorAddr: mockValidator{ + blsPubKey: blsSecretKey.PublicKey(), + }, }, } recents, _ := arc.NewARC[common.Hash, *Snapshot](inmemorySnapshots) @@ -2091,8 +2113,10 @@ func TestSystemTransactionOrder(t *testing.T) { }).MustCommit(db) mock := &mockContract{ - validators: map[common.Address]blsCommon.PublicKey{ - validatorAddr: blsSecretKey.PublicKey(), + validators: map[common.Address]mockValidator{ + validatorAddr: mockValidator{ + blsPubKey: blsSecretKey.PublicKey(), + }, }, } recents, _ := arc.NewARC[common.Hash, *Snapshot](inmemorySnapshots) @@ -2219,7 +2243,7 @@ func TestIsPeriodBlock(t *testing.T) { recents, _ := arc.NewARC[common.Hash, *Snapshot](inmemorySnapshots) signatures, _ := arc.NewARC[common.Hash, common.Address](inmemorySignatures) mock := &mockContract{ - validators: map[common.Address]blsCommon.PublicKey{}, + validators: map[common.Address]mockValidator{}, } c := &Consortium{ chainConfig: &chainConfig, @@ -2231,29 +2255,25 @@ func TestIsPeriodBlock(t *testing.T) { db: db, contract: mock, } - validators := make([]common.Address, NUM_OF_VALIDATORS) - for i := 0; i < NUM_OF_VALIDATORS; i++ { - validators = append(validators, common.BigToAddress(big.NewInt(int64(i)))) - } - var header = &types.Header{} - // header of block 0 - // this must not a period block - header = genesis.Header() - if c.IsPeriodBlock(chain, header, nil) { - t.Errorf("wrong period block") - } - // header of block 200 // this must not a period block header = bs[199].Header() - if c.IsPeriodBlock(chain, header, nil) { + isPeriodBlock, err := c.IsPeriodBlock(chain, header, nil) + if err != nil { + t.Fatal(err) + } + if isPeriodBlock { t.Error("wrong period block") } header = bs[351].Header() - if c.IsPeriodBlock(chain, header, nil) { + isPeriodBlock, err = c.IsPeriodBlock(chain, header, nil) + if err != nil { + t.Fatal(err) + } + if isPeriodBlock { t.Error("wrong period block") } @@ -2274,18 +2294,251 @@ func TestIsPeriodBlock(t *testing.T) { // this must be a period block header = bs[399].Header() // this header must be period header - if !c.IsPeriodBlock(chain, header, nil) { + isPeriodBlock, err = c.IsPeriodBlock(chain, header, nil) + if err != nil { + t.Fatal(err) + } + if !isPeriodBlock { t.Errorf("wrong period block") } // header of block 500 // this must not be a period block header = bs[499].Header() - if c.IsPeriodBlock(chain, header, nil) { + isPeriodBlock, err = c.IsPeriodBlock(chain, header, nil) + if err != nil { + t.Fatal(err) + } + if isPeriodBlock { t.Errorf("wrong period block") } } +func generateChain( + t *testing.T, + genesis *types.Block, + chainConfig *params.ChainConfig, + chain *core.BlockChain, + v2 *Consortium, + db ethdb.Database, + secretKey *ecdsa.PrivateKey, + validatorAddr common.Address, + blsSecretKey blsCommon.SecretKey, + bumpTimeStampLastBlockOfEpoch bool, +) []*types.Block { + extraData := finality.HeaderExtraData{} + encodedExtraData, err := extraData.EncodeV2(chainConfig, common.Big1) + if err != nil { + t.Fatal(err) + } + + parent := genesis + var block []*types.Block + for i := 0; i < 400; i++ { + block, _ = core.GenerateChain( + chainConfig, + parent, + v2, + db, + 1, + func(_ int, bg *core.BlockGen) { + if i == 0 { + bg.OffsetTime(int64(dayInSeconds)) + } + + var blockExtraData = encodedExtraData + if i == 199 { + extraData := finality.HeaderExtraData{ + BlockProducersBitSet: 1, + CheckpointValidators: []finality.ValidatorWithBlsPub{ + { + Address: validatorAddr, + BlsPublicKey: blsSecretKey.PublicKey(), + Weight: consortiumCommon.MaxFinalityVotePercentage, + }, + }, + } + encodedExtraData, err := extraData.EncodeV2(chainConfig, common.Big1) + if err != nil { + t.Fatal(err) + } + blockExtraData = encodedExtraData + } + bg.SetCoinbase(validatorAddr) + bg.SetExtra(blockExtraData[:]) + if bumpTimeStampLastBlockOfEpoch { + if i == 398 { + // Make block 399 to a new date + bg.OffsetTime(int64(dayInSeconds)) + } + } else if i == 399 { + // Make block 400 to a new date + bg.OffsetTime(int64(dayInSeconds)) + } + bg.SetDifficulty(big.NewInt(7)) + }, + true, + ) + + header := block[0].Header() + hash := calculateSealHash(header, big.NewInt(2021)) + sig, err := crypto.Sign(hash[:], secretKey) + if err != nil { + t.Fatalf("Failed to sign block, err %s", err) + } + + copy(header.Extra[len(header.Extra)-consortiumCommon.ExtraSeal:], sig) + block[0] = block[0].WithSeal(header) + parent = block[0] + + if i != 399 { + _, err = chain.InsertChain(block, nil) + if err != nil { + t.Fatalf("Failed to insert chain, err %s", err) + } + } + } + + return block +} + +func createNewChain( + chainConfig *params.ChainConfig, + validatorAddr common.Address, + blsSecretKey blsCommon.SecretKey, +) (*types.Block, *core.BlockChain, ethdb.Database, *Consortium) { + db := rawdb.NewMemoryDatabase() + genesis := (&core.Genesis{ + Config: chainConfig, + }).MustCommit(db) + + mock := &mockContract{ + validators: map[common.Address]mockValidator{ + validatorAddr: mockValidator{ + blsPubKey: blsSecretKey.PublicKey(), + stakedAmount: big.NewInt(10), + }, + }, + maxValidatorNumber: common.Big1, + } + recents, _ := arc.NewARC[common.Hash, *Snapshot](inmemorySnapshots) + signatures, _ := arc.NewARC[common.Hash, common.Address](inmemorySignatures) + + v2 := &Consortium{ + chainConfig: chainConfig, + contract: mock, + recents: recents, + signatures: signatures, + fakeDiff: true, + db: db, + config: ¶ms.ConsortiumConfig{ + EpochV2: 200, + }, + } + + chain, _ := core.NewBlockChain(db, nil, chainConfig, v2, vm.Config{}, nil, nil) + return genesis, chain, db, v2 +} + +func TestIsPeriodVenoki(t *testing.T) { + blsSecretKey, err := blst.RandKey() + if err != nil { + t.Fatal(err) + } + secretKey, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + validatorAddr := crypto.PubkeyToAddress(secretKey.PublicKey) + + chainConfig := params.ChainConfig{ + ChainID: big.NewInt(2021), + HomesteadBlock: common.Big0, + EIP150Block: common.Big0, + EIP155Block: common.Big0, + EIP158Block: common.Big0, + ConsortiumV2Block: common.Big0, + MikoBlock: common.Big0, + ShillinBlock: common.Big1, + TrippBlock: common.Big1, + TrippPeriod: common.Big0, + AaronBlock: common.Big1, + Consortium: ¶ms.ConsortiumConfig{ + EpochV2: 200, + }, + TransparentProxyCodeUpgrade: ¶ms.ContractCodeUpgrade{ + Code: hexutil.Bytes{}, + AxieAddress: common.Address{}, + LandAddress: common.Address{}, + }, + } + + genesis, chain, db, v2 := createNewChain(&chainConfig, validatorAddr, blsSecretKey) + block := generateChain(t, genesis, &chainConfig, chain, v2, db, secretKey, validatorAddr, blsSecretKey, false) + + // Case 1 + // Before Venoki, the first block of epoch passes 0:00 must be the + // start of new period + isPeriodBlock, err := v2.IsPeriodBlock(chain, block[0].Header(), nil) + if err != nil { + t.Fatal(err) + } + if !isPeriodBlock { + t.Fatalf("Expect isPeriodBlock before Venoki, got: %v", isPeriodBlock) + } + snapshot, err := v2.snapshot(chain, block[0].NumberU64(), block[0].Hash(), []*types.Header{block[0].Header()}) + if err != nil { + t.Fatal(err) + } + // Snapshot period number must be the period of first block of epoch (block 400) which is 2 + if snapshot.CurrentPeriod != 2 { + t.Fatalf("Mismatch period block before Venoki, exp: %v, got: %v", 2, snapshot.CurrentPeriod) + } + + // Case 2 + // After Venoki, the start of new period must be when the last block of + // previous epoch passes 0:00 + // The last block of previous epoch does not pass 0:00 + chainConfig.VenokiBlock = big.NewInt(200) + genesis, chain, db, v2 = createNewChain(&chainConfig, validatorAddr, blsSecretKey) + block = generateChain(t, genesis, &chainConfig, chain, v2, db, secretKey, validatorAddr, blsSecretKey, false) + isPeriodBlock, err = v2.IsPeriodBlock(chain, block[0].Header(), nil) + if err != nil { + t.Fatal(err) + } + if isPeriodBlock { + t.Fatalf("Expect not isPeriodBlock after Venoki, got: %v", isPeriodBlock) + } + snapshot, err = v2.snapshot(chain, block[0].NumberU64(), block[0].Hash(), []*types.Header{block[0].Header()}) + if err != nil { + t.Fatal(err) + } + // Snapshot period number must be the period of last block of previous epoch (block 399) which is 1 + if snapshot.CurrentPeriod != 1 { + t.Fatalf("Mismatch period block after Venoki, exp: %v, got: %v", 1, snapshot.CurrentPeriod) + } + + // Case 3 + // The last block of previous epoch passes 0:00 + genesis, chain, db, v2 = createNewChain(&chainConfig, validatorAddr, blsSecretKey) + block = generateChain(t, genesis, &chainConfig, chain, v2, db, secretKey, validatorAddr, blsSecretKey, true) + isPeriodBlock, err = v2.IsPeriodBlock(chain, block[0].Header(), nil) + if err != nil { + t.Fatal(err) + } + if !isPeriodBlock { + t.Fatalf("Expect isPeriodBlock after Venoki, got: %v", isPeriodBlock) + } + snapshot, err = v2.snapshot(chain, block[0].NumberU64(), block[0].Hash(), []*types.Header{block[0].Header()}) + if err != nil { + t.Fatal(err) + } + // Snapshot period number must be the period of last block of previous epoch (block 399) which is 2 + if snapshot.CurrentPeriod != 2 { + t.Fatalf("Mismatch period block after Venoki, exp: %v, got: %v", 2, snapshot.CurrentPeriod) + } +} + func TestIsTrippEffective(t *testing.T) { now := uint64(time.Now().Unix()) midnight := uint64(now / dayInSeconds * dayInSeconds) @@ -2315,7 +2568,7 @@ func TestIsTrippEffective(t *testing.T) { recents, _ := arc.NewARC[common.Hash, *Snapshot](inmemorySnapshots) signatures, _ := arc.NewARC[common.Hash, common.Address](inmemorySignatures) mock := &mockContract{ - validators: map[common.Address]blsCommon.PublicKey{}, + validators: map[common.Address]mockValidator{}, } c := &Consortium{ chainConfig: &chainConfig, diff --git a/consensus/consortium/v2/snapshot.go b/consensus/consortium/v2/snapshot.go index 2d1995531..2d051d5c9 100644 --- a/consensus/consortium/v2/snapshot.go +++ b/consensus/consortium/v2/snapshot.go @@ -42,7 +42,12 @@ type Snapshot struct { JustifiedBlockNumber uint64 `json:"justifiedBlockNumber,omitempty"` // The justified block number JustifiedBlockHash common.Hash `json:"justifiedBlockHash,omitempty"` // The justified block hash - CurrentPeriod uint64 `json:"currentPeriod,omitempty"` // Period number where the snapshot was created + + // After Tripp before Venoki, the period number of an epoch calculated based on the timestamp + // of first block in epoch + // After Venoki, the period number of an epoch calculated based on the the timestamp of last block + // in the previous epoch + CurrentPeriod uint64 `json:"currentPeriod,omitempty"` } // validatorsAscending implements the sort interface to allow sorting a list of addresses @@ -259,8 +264,17 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea } } - if chainRules.IsTripp && number%s.config.EpochV2 == 0 && header.Time/dayInSeconds > snap.CurrentPeriod { - snap.CurrentPeriod = header.Time / dayInSeconds + // First block of an epoch + if number%s.config.EpochV2 == 0 { + if chainRules.IsVenoki { + parentHeader := FindAncientHeader(header, 1, chain, parents) + if parentHeader == nil { + return nil, consensus.ErrUnknownAncestor + } + snap.CurrentPeriod = parentHeader.Time / dayInSeconds + } else if chainRules.IsTripp { + snap.CurrentPeriod = header.Time / dayInSeconds + } } // Change the validator set base on the size of the validators set if number > 0 && number%s.config.EpochV2 == uint64(len(snap.validators())/2) {