Skip to content

Commit d548f1a

Browse files
authored
Store empty pending block instead of deleting the key (#1241)
To decrease the number of `block not found` errors when simulating transactions against the pending block.
1 parent 935eb23 commit d548f1a

File tree

2 files changed

+113
-36
lines changed

2 files changed

+113
-36
lines changed

blockchain/blockchain.go

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ func (b *Blockchain) Store(block *core.Block, blockCommitments *core.BlockCommit
339339
return err
340340
}
341341

342-
if err := txn.Delete(db.Pending.Key()); err != nil {
342+
if err := storeEmptyPending(txn, block.Header); err != nil {
343343
return err
344344
}
345345

@@ -822,14 +822,6 @@ func (b *Blockchain) revertHead(txn db.Transaction) error {
822822
return err
823823
}
824824
}
825-
if !genesisBlock {
826-
var newHeader *core.Header
827-
newHeader, err = blockHeaderByNumber(txn, blockNumber-1)
828-
if err != nil {
829-
return err
830-
}
831-
b.newHeads.Send(newHeader)
832-
}
833825

834826
if err = removeTxsAndReceipts(txn, blockNumber, header.TransactionCount); err != nil {
835827
return err
@@ -840,16 +832,26 @@ func (b *Blockchain) revertHead(txn db.Transaction) error {
840832
return err
841833
}
842834

843-
// remove pending
844-
if err = txn.Delete(db.Pending.Key()); err != nil {
845-
return err
846-
}
835+
// Revert chain height and pending.
847836

848-
// update chain height
849837
if genesisBlock {
838+
if err = txn.Delete(db.Pending.Key()); err != nil {
839+
return err
840+
}
850841
return txn.Delete(db.ChainHeight.Key())
851842
}
852843

844+
var newHeader *core.Header
845+
newHeader, err = blockHeaderByNumber(txn, blockNumber-1)
846+
if err != nil {
847+
return err
848+
}
849+
b.newHeads.Send(newHeader)
850+
851+
if err := storeEmptyPending(txn, newHeader); err != nil {
852+
return err
853+
}
854+
853855
heightBin := core.MarshalBlockNumber(blockNumber - 1)
854856
return txn.Set(db.ChainHeight.Key(), heightBin)
855857
}
@@ -881,36 +883,71 @@ func removeTxsAndReceipts(txn db.Transaction, blockNumber, numTxs uint64) error
881883
return nil
882884
}
883885

886+
func storeEmptyPending(txn db.Transaction, latestHeader *core.Header) error {
887+
receipts := make([]*core.TransactionReceipt, 0)
888+
pendingBlock := &core.Block{
889+
Header: &core.Header{
890+
ParentHash: latestHeader.Hash,
891+
SequencerAddress: latestHeader.SequencerAddress,
892+
Timestamp: latestHeader.Timestamp + 1,
893+
ProtocolVersion: latestHeader.ProtocolVersion,
894+
EventsBloom: core.EventsBloom(receipts),
895+
GasPrice: latestHeader.GasPrice,
896+
},
897+
Transactions: make([]core.Transaction, 0),
898+
Receipts: receipts,
899+
}
900+
901+
emptyPending := &Pending{
902+
Block: pendingBlock,
903+
StateUpdate: &core.StateUpdate{
904+
OldRoot: latestHeader.GlobalStateRoot,
905+
StateDiff: &core.StateDiff{
906+
StorageDiffs: make(map[felt.Felt][]core.StorageDiff, 0),
907+
Nonces: make(map[felt.Felt]*felt.Felt, 0),
908+
DeployedContracts: make([]core.DeployedContract, 0),
909+
DeclaredV0Classes: make([]*felt.Felt, 0),
910+
DeclaredV1Classes: make([]core.DeclaredV1Class, 0),
911+
ReplacedClasses: make([]core.ReplacedClass, 0),
912+
},
913+
},
914+
NewClasses: make(map[felt.Felt]core.Class, 0),
915+
}
916+
return storePending(txn, emptyPending)
917+
}
918+
884919
// StorePending stores a pending block given that it is for the next height
885920
func (b *Blockchain) StorePending(pending *Pending) error {
886921
return b.database.Update(func(txn db.Transaction) error {
887-
expectedParent := new(felt.Felt)
888-
expectedOldRoot := new(felt.Felt)
889-
h, err := head(txn)
922+
expectedParentHash := new(felt.Felt)
923+
h, err := headsHeader(txn)
890924
if err != nil && !errors.Is(err, db.ErrKeyNotFound) {
891925
return err
892926
} else if err == nil {
893-
expectedParent = h.Hash
894-
expectedOldRoot = h.GlobalStateRoot
927+
expectedParentHash = h.Hash
895928
}
896929

897-
if !expectedParent.Equal(pending.Block.ParentHash) || !expectedOldRoot.Equal(pending.StateUpdate.OldRoot) {
898-
return errors.New("pending block parent is not our local HEAD")
930+
if !expectedParentHash.Equal(pending.Block.ParentHash) {
931+
return ErrParentDoesNotMatchHead
899932
}
900933

901934
existingPending, err := pendingBlock(txn)
902935
if err == nil && existingPending.Block.TransactionCount >= pending.Block.TransactionCount {
903936
return nil // ignore the incoming pending if it has fewer transactions than the one we already have
904937
}
905938

906-
pendingBytes, err := encoder.Marshal(pending)
907-
if err != nil {
908-
return err
909-
}
910-
return txn.Set(db.Pending.Key(), pendingBytes)
939+
return storePending(txn, pending)
911940
})
912941
}
913942

943+
func storePending(txn db.Transaction, pending *Pending) error {
944+
pendingBytes, err := encoder.Marshal(pending)
945+
if err != nil {
946+
return err
947+
}
948+
return txn.Set(db.Pending.Key(), pendingBytes)
949+
}
950+
914951
func pendingBlock(txn db.Transaction) (Pending, error) {
915952
var pending Pending
916953
err := txn.Get(db.Pending.Key(), func(bytes []byte) error {

blockchain/blockchain_test.go

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,11 @@ func TestPending(t *testing.T) {
652652
su, err := gw.StateUpdate(context.Background(), 0)
653653
require.NoError(t, err)
654654

655+
t.Run("pending state shouldnt exist if no pending block", func(t *testing.T) {
656+
_, _, err = chain.PendingState()
657+
require.Error(t, err)
658+
})
659+
655660
t.Run("store genesis as pending", func(t *testing.T) {
656661
pendingGenesis := blockchain.Pending{
657662
Block: b,
@@ -664,10 +669,50 @@ func TestPending(t *testing.T) {
664669
assert.Equal(t, pendingGenesis, gotPending)
665670
})
666671

667-
t.Run("storing genesis as an accepted block should clear pending", func(t *testing.T) {
668-
require.NoError(t, chain.Store(b, &emptyCommitments, su, nil))
669-
_, pErr := chain.Pending()
670-
require.ErrorIs(t, pErr, db.ErrKeyNotFound)
672+
require.NoError(t, chain.Store(b, &emptyCommitments, su, nil))
673+
674+
t.Run("no pending block means pending state matches head state", func(t *testing.T) {
675+
pending, pErr := chain.Pending()
676+
require.NoError(t, pErr)
677+
require.Equal(t, b.Timestamp+1, pending.Block.Timestamp)
678+
require.Equal(t, b.SequencerAddress, pending.Block.SequencerAddress)
679+
require.Equal(t, b.GasPrice, pending.Block.GasPrice)
680+
require.Equal(t, b.ProtocolVersion, pending.Block.ProtocolVersion)
681+
require.Equal(t, su.NewRoot, pending.StateUpdate.OldRoot)
682+
require.Empty(t, pending.StateUpdate.StateDiff.Nonces)
683+
require.Empty(t, pending.StateUpdate.StateDiff.StorageDiffs)
684+
require.Empty(t, pending.StateUpdate.StateDiff.ReplacedClasses)
685+
require.Empty(t, pending.StateUpdate.StateDiff.DeclaredV0Classes)
686+
require.Empty(t, pending.StateUpdate.StateDiff.DeclaredV1Classes)
687+
require.Empty(t, pending.StateUpdate.StateDiff.DeployedContracts)
688+
require.Empty(t, pending.NewClasses)
689+
690+
// PendingState matches head state.
691+
require.NoError(t, pErr)
692+
reader, closer, pErr := chain.PendingState()
693+
require.NoError(t, pErr)
694+
t.Cleanup(func() {
695+
require.NoError(t, closer())
696+
})
697+
698+
for address, diff := range su.StateDiff.StorageDiffs {
699+
for _, kv := range diff {
700+
value, csErr := reader.ContractStorage(&address, kv.Key)
701+
require.NoError(t, csErr)
702+
require.Equal(t, kv.Value, value)
703+
}
704+
}
705+
706+
for address, nonce := range su.StateDiff.Nonces {
707+
got, cnErr := reader.ContractNonce(&address)
708+
require.NoError(t, cnErr)
709+
require.Equal(t, nonce, got)
710+
}
711+
712+
for _, hash := range su.StateDiff.DeclaredV0Classes {
713+
_, err = reader.Class(hash)
714+
require.NoError(t, err)
715+
}
671716
})
672717

673718
t.Run("storing a pending too far into the future should fail", func(t *testing.T) {
@@ -680,12 +725,7 @@ func TestPending(t *testing.T) {
680725
Block: b,
681726
StateUpdate: su,
682727
}
683-
require.EqualError(t, chain.StorePending(&notExpectedPending), "pending block parent is not our local HEAD")
684-
})
685-
686-
t.Run("pending state shouldnt exist if no pending block", func(t *testing.T) {
687-
_, _, err = chain.PendingState()
688-
require.Error(t, err)
728+
require.ErrorIs(t, chain.StorePending(&notExpectedPending), blockchain.ErrParentDoesNotMatchHead)
689729
})
690730

691731
t.Run("store expected pending block", func(t *testing.T) {

0 commit comments

Comments
 (0)