Skip to content

Commit

Permalink
Allow storing Pending blocks in blockchain
Browse files Browse the repository at this point in the history
  • Loading branch information
omerfirmak committed Jun 14, 2023
1 parent 976d896 commit 8313c7e
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 0 deletions.
59 changes: 59 additions & 0 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type Reader interface {
StateAtBlockNumber(blockNumber uint64) (core.StateReader, StateCloser, error)

EventFilter(from *felt.Felt, keys []*felt.Felt) (*EventFilter, error)

Pending() (Pending, error)
}

var (
Expand Down Expand Up @@ -274,6 +276,10 @@ func (b *Blockchain) Store(block *core.Block, stateUpdate *core.StateUpdate, new
return err
}

if err := txn.Delete(db.Pending.Key()); err != nil {
return err
}

// Head of the blockchain is maintained as follows:
// [db.ChainHeight]() -> (BlockNumber)
heightBin := make([]byte, lenOfByteSlice)
Expand Down Expand Up @@ -747,6 +753,11 @@ func (b *Blockchain) revertHead(txn db.Transaction) error {
return err
}

// remove pending
if err := txn.Delete(db.Pending.Key()); err != nil {
return err
}

// update chain height
if blockNumber == 0 {
return txn.Delete(db.ChainHeight.Key())
Expand All @@ -756,3 +767,51 @@ func (b *Blockchain) revertHead(txn db.Transaction) error {
binary.BigEndian.PutUint64(heightBin, blockNumber-1)
return txn.Set(db.ChainHeight.Key(), heightBin)
}

// StorePending stores a pending block given that it is for the next height
func (b *Blockchain) StorePending(pending *Pending) error {
return b.database.Update(func(txn db.Transaction) error {
expectedParent := new(felt.Felt)
expectedOldRoot := new(felt.Felt)
head, err := b.head(txn)
if err != nil && !errors.Is(err, db.ErrKeyNotFound) {
return err
} else if err == nil {
expectedParent = head.Hash
expectedOldRoot = head.GlobalStateRoot
}

if !expectedParent.Equal(pending.Block.ParentHash) || !expectedOldRoot.Equal(pending.StateUpdate.OldRoot) {
return errors.New("pending block parent is not our local HEAD")
}

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

pendingBytes, err := encoder.Marshal(pending)
if err != nil {
return err
}
return txn.Set(db.Pending.Key(), pendingBytes)
})
}

func pendingBlock(txn db.Transaction) (Pending, error) {
var pending Pending
err := txn.Get(db.Pending.Key(), func(bytes []byte) error {
return encoder.Unmarshal(bytes, &pending)
})
return pending, err
}

// Pending returns the pending block from the database
func (b *Blockchain) Pending() (Pending, error) {
var pending Pending
return pending, b.database.View(func(txn db.Transaction) error {
var err error
pending, err = pendingBlock(txn)
return err
})
}
64 changes: 64 additions & 0 deletions blockchain/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,3 +594,67 @@ func TestL1Update(t *testing.T) {
})
}
}

func TestPending(t *testing.T) {
testDB := pebble.NewMemTest()
t.Cleanup(func() {
require.NoError(t, testDB.Close())
})
chain := blockchain.New(testDB, utils.MAINNET, utils.NewNopZapLogger())
client, closeFn := feeder.NewTestClient(utils.MAINNET)
t.Cleanup(closeFn)
gw := adaptfeeder.New(client)

b, err := gw.BlockByNumber(context.Background(), 0)
require.NoError(t, err)
su, err := gw.StateUpdate(context.Background(), 0)
require.NoError(t, err)

t.Run("store genesis as pending", func(t *testing.T) {
pendingGenesis := blockchain.Pending{
Block: b,
StateUpdate: su,
}
require.NoError(t, chain.StorePending(&pendingGenesis))

gotPending, pErr := chain.Pending()
require.NoError(t, pErr)
assert.Equal(t, pendingGenesis, gotPending)
})

t.Run("storing genesis as an accepted block should clear pending", func(t *testing.T) {
require.NoError(t, chain.Store(b, su, nil))
_, pErr := chain.Pending()
require.ErrorIs(t, pErr, db.ErrKeyNotFound)
})

t.Run("storing a pending too far into the future should fail", func(t *testing.T) {
b, err = gw.BlockByNumber(context.Background(), 2)
require.NoError(t, err)
su, err = gw.StateUpdate(context.Background(), 2)
require.NoError(t, err)

notExpectedPending := blockchain.Pending{
Block: b,
StateUpdate: su,
}
require.EqualError(t, chain.StorePending(&notExpectedPending), "pending block parent is not our local HEAD")
})

t.Run("store expected pending block", func(t *testing.T) {
b, err = gw.BlockByNumber(context.Background(), 1)
require.NoError(t, err)
su, err = gw.StateUpdate(context.Background(), 1)
require.NoError(t, err)

expectedPending := blockchain.Pending{
Block: b,
StateUpdate: su,
}
require.NoError(t, chain.StorePending(&expectedPending))

gotPending, pErr := chain.Pending()
require.NoError(t, pErr)
assert.Equal(t, expectedPending, gotPending)
})
}
12 changes: 12 additions & 0 deletions blockchain/pending.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package blockchain

import (
"github.com/NethermindEth/juno/core"
"github.com/NethermindEth/juno/core/felt"
)

type Pending struct {
Block *core.Block
StateUpdate *core.StateUpdate
NewClasses map[felt.Felt]core.Class
}
1 change: 1 addition & 0 deletions db/buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
ContractDeploymentHeight
L1Height
SchemaVersion
Pending
)

// Key flattens a prefix and series of byte arrays into a single []byte.
Expand Down
15 changes: 15 additions & 0 deletions mocks/mock_blockchain.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8313c7e

Please sign in to comment.