Skip to content

Commit

Permalink
Move pending to sync package
Browse files Browse the repository at this point in the history
In order to moved handling of pending to synchroniser the following
changes needed to be made:
- Add database to synchroniser, so that pending state can be served
- Blockchain and Events Filter have a pendingBlockFn() which returns the
  pending block. Due to import cycle pending struct could not be
  referenced, therefore, the anonymous function is passed.
- Add PendingBlock() to return just the pending block, this was mainly
  added to support the pendingBlockFn().
- In rpc package the pending block and state is retrieved through
  synchroniser. Therefore, receipt and transaction handler now check the
  pending block for the requested transaction/receipt.
  • Loading branch information
IronGauntlets committed Apr 29, 2024
1 parent 3c798aa commit 5e2d6e6
Show file tree
Hide file tree
Showing 24 changed files with 490 additions and 487 deletions.
155 changes: 12 additions & 143 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/binary"
"errors"
"fmt"
"sync/atomic"

"github.com/Masterminds/semver/v3"
"github.com/NethermindEth/juno/core"
Expand Down Expand Up @@ -37,24 +36,20 @@ type Reader interface {
HeadState() (core.StateReader, StateCloser, error)
StateAtBlockHash(blockHash *felt.Felt) (core.StateReader, StateCloser, error)
StateAtBlockNumber(blockNumber uint64) (core.StateReader, StateCloser, error)
PendingState() (core.StateReader, StateCloser, error)

BlockCommitmentsByNumber(blockNumber uint64) (*core.BlockCommitments, error)

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

Pending() (*Pending, error)

Network() *utils.Network
}

var (
ErrParentDoesNotMatchHead = errors.New("block's parent hash does not match head block hash")
ErrPendingBlockNotFound = errors.New("pending block not found")
supportedStarknetVersion = semver.MustParse("0.13.1")
)

func checkBlockVersion(protocolVersion string) error {
func CheckBlockVersion(protocolVersion string) error {
blockVer, err := core.ParseBlockVersion(protocolVersion)
if err != nil {
return err
Expand All @@ -69,22 +64,21 @@ func checkBlockVersion(protocolVersion string) error {

var _ Reader = (*Blockchain)(nil)

// Todo: Remove after pending is moved to sychcroniser
var pending atomic.Pointer[Pending]

// Blockchain is responsible for keeping track of all things related to the Starknet blockchain
type Blockchain struct {
network *utils.Network
database db.DB
listener EventListener
network *utils.Network
database db.DB
listener EventListener
pendingBlockFn func() *core.Block
}

func New(database db.DB, network *utils.Network) *Blockchain {
func New(database db.DB, network *utils.Network, pendingBlockFn func() *core.Block) *Blockchain {
RegisterCoreTypesToEncoder()
return &Blockchain{
database: database,
network: network,
listener: &SelectiveListener{},
database: database,
network: network,
listener: &SelectiveListener{},
pendingBlockFn: pendingBlockFn,
}
}

Expand Down Expand Up @@ -244,24 +238,6 @@ func (b *Blockchain) TransactionByHash(hash *felt.Felt) (core.Transaction, error
return transaction, b.database.View(func(txn db.Transaction) error {
var err error
transaction, err = transactionByHash(txn, hash)

// not found in the canonical blocks, try pending
if errors.Is(err, db.ErrKeyNotFound) {
var pending *Pending
pending, err = pendingBlock(txn)
if err != nil {
return err
}

for _, t := range pending.Block.Transactions {
if hash.Equal(t.Hash()) {
transaction = t
return nil
}
}
return db.ErrKeyNotFound
}

return err
})
}
Expand All @@ -277,29 +253,6 @@ func (b *Blockchain) Receipt(hash *felt.Felt) (*core.TransactionReceipt, *felt.F
return receipt, blockHash, blockNumber, b.database.View(func(txn db.Transaction) error {
var err error
receipt, blockHash, blockNumber, err = receiptByHash(txn, hash)

// not found in the canonical blocks, try pending
if errors.Is(err, db.ErrKeyNotFound) {
var pending *Pending
pending, err = pendingBlock(txn)
if err != nil {
if !errors.Is(err, ErrPendingBlockNotFound) {
return err
}
return db.ErrKeyNotFound
}

for i, t := range pending.Block.Transactions {
if hash.Equal(t.Hash()) {
receipt = pending.Block.Receipts[i]
blockHash = nil
blockNumber = 0
return nil
}
}
return db.ErrKeyNotFound
}

return err
})
}
Expand Down Expand Up @@ -380,7 +333,7 @@ func (b *Blockchain) VerifyBlock(block *core.Block) error {
}

func verifyBlock(txn db.Transaction, block *core.Block) error {
if err := checkBlockVersion(block.ProtocolVersion); err != nil {
if err := CheckBlockVersion(block.ProtocolVersion); err != nil {
return err
}

Expand Down Expand Up @@ -818,7 +771,7 @@ func (b *Blockchain) EventFilter(from *felt.Felt, keys [][]felt.Felt) (*EventFil
return nil, err
}

return newEventFilter(txn, from, keys, 0, latest, &pending), nil
return newEventFilter(txn, from, keys, 0, latest, b.pendingBlockFn), nil
}

// RevertHead reverts the head block
Expand Down Expand Up @@ -906,87 +859,3 @@ func removeTxsAndReceipts(txn db.Transaction, blockNumber, numTxs uint64) error

return nil
}

// StorePending stores a pending block given that it is for the next height
func (b *Blockchain) StorePending(p *Pending) error {
err := checkBlockVersion(p.Block.ProtocolVersion)
if err != nil {
return err
}
return b.database.View(func(txn db.Transaction) error {
expectedParentHash := new(felt.Felt)
h, err := headsHeader(txn)
if err != nil && !errors.Is(err, db.ErrKeyNotFound) {
return err
} else if err == nil {
expectedParentHash = h.Hash
}

if !expectedParentHash.Equal(p.Block.ParentHash) {
return ErrParentDoesNotMatchHead
}

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

if h != nil {
p.Block.Number = h.Number + 1
}
pending.Store(p)

return nil
})
}

func pendingBlock(txn db.Transaction) (*Pending, error) {
p := pending.Load()
if p == nil {
return nil, ErrPendingBlockNotFound
}

expectedParentHash := &felt.Zero
if head, err := headsHeader(txn); err == nil {
expectedParentHash = head.Hash
}
if p.Block.ParentHash.Equal(expectedParentHash) {
return p, nil
}

// Since the pending block in the cache is outdated remove it
pending.Store(nil)

return nil, ErrPendingBlockNotFound
}

// Pending returns the pending block from the database
func (b *Blockchain) Pending() (*Pending, error) {
b.listener.OnRead("Pending")
var pending *Pending
return pending, b.database.View(func(txn db.Transaction) error {
var err error
pending, err = pendingBlock(txn)
return err
})
}

// PendingState returns the state resulting from execution of the pending block
func (b *Blockchain) PendingState() (core.StateReader, StateCloser, error) {
b.listener.OnRead("PendingState")
txn, err := b.database.NewTransaction(false)
if err != nil {
return nil, nil, err
}

pending, err := pendingBlock(txn)
if err != nil {
return nil, nil, utils.RunAndWrapOnError(txn.Discard, err)
}

return NewPendingState(pending.StateUpdate.StateDiff, pending.NewClasses, core.NewState(txn)), txn.Discard, nil
}
Loading

0 comments on commit 5e2d6e6

Please sign in to comment.