Limiting Geth to one transaction per block (#3)
* Limiting Geth to one transaction per block * Adding TransitionBatchBuilder to build & submit rollup blocks
This commit is contained in:
parent
aae5230baf
commit
e14cfa5576
1
.gitignore
vendored
1
.gitignore
vendored
@ -34,6 +34,7 @@ profile.cov
|
|||||||
|
|
||||||
# IdeaIDE
|
# IdeaIDE
|
||||||
.idea
|
.idea
|
||||||
|
*.iml
|
||||||
|
|
||||||
# VS Code
|
# VS Code
|
||||||
.vscode
|
.vscode
|
||||||
|
@ -13,4 +13,10 @@ RUN apk add --no-cache ca-certificates
|
|||||||
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
||||||
|
|
||||||
EXPOSE 8545 8546 8547 30303 30303/udp
|
EXPOSE 8545 8546 8547 30303 30303/udp
|
||||||
ENTRYPOINT ["geth"]
|
# ENTRYPOINT ["geth"]
|
||||||
|
|
||||||
|
COPY docker/entrypoint.sh /bin
|
||||||
|
RUN chmod +x /bin/entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 9545
|
||||||
|
ENTRYPOINT ["sh", "/bin/entrypoint.sh"]
|
||||||
|
21
docker/entrypoint.sh
Normal file
21
docker/entrypoint.sh
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
## Passed in from environment variables:
|
||||||
|
# HOSTNAME=
|
||||||
|
# PORT=8545
|
||||||
|
# NETWORK_ID=108
|
||||||
|
CLEAR_DATA_FILE_PATH="${VOLUME_PATH}/.clear_data_key_${CLEAR_DATA_KEY}"
|
||||||
|
TARGET_GAS_LIMIT=${TARGET_GAS_LIMIT:-4294967295}
|
||||||
|
|
||||||
|
if [[ -n "$CLEAR_DATA_KEY" && ! -f "$CLEAR_DATA_FILE_PATH" ]]; then
|
||||||
|
echo "Detected change in CLEAR_DATA_KEY. Purging data."
|
||||||
|
rm -rf ${VOLUME_PATH}/*
|
||||||
|
rm -rf ${VOLUME_PATH}/.clear_data_key_*
|
||||||
|
echo "Local data cleared from '${VOLUME_PATH}/*'"
|
||||||
|
echo "Contents of volume dir: $(ls -alh $VOLUME_PATH)"
|
||||||
|
touch $CLEAR_DATA_FILE_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting Geth..."
|
||||||
|
## Command to kick off geth
|
||||||
|
geth --dev --datadir $VOLUME_PATH --rpc --rpcaddr $HOSTNAME --rpcvhosts=* --rpcport $PORT --networkid $NETWORK_ID --rpcapi 'eth,net' --gasprice '0' --targetgaslimit $TARGET_GAS_LIMIT --nousb --gcmode=archive --verbosity "6"
|
@ -20,10 +20,12 @@ package eth
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/rollup"
|
||||||
"math/big"
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
@ -207,7 +209,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||||||
if checkpoint == nil {
|
if checkpoint == nil {
|
||||||
checkpoint = params.TrustedCheckpoints[genesisHash]
|
checkpoint = params.TrustedCheckpoints[genesisHash]
|
||||||
}
|
}
|
||||||
if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil {
|
blockSubmitter := rollup.NewBlockSubmitter()
|
||||||
|
rollupBlockBuilder, e := rollup.NewTransitionBatchBuilder(chainDb, eth.blockchain, blockSubmitter, 5 * time.Minute, 100_000_000_000, 200)
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist, rollupBlockBuilder); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
|
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/rollup"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
@ -77,6 +78,8 @@ type ProtocolManager struct {
|
|||||||
blockchain *core.BlockChain
|
blockchain *core.BlockChain
|
||||||
maxPeers int
|
maxPeers int
|
||||||
|
|
||||||
|
rollupBlockBuilder *rollup.TransitionBatchBuilder
|
||||||
|
|
||||||
downloader *downloader.Downloader
|
downloader *downloader.Downloader
|
||||||
fetcher *fetcher.Fetcher
|
fetcher *fetcher.Fetcher
|
||||||
peers *peerSet
|
peers *peerSet
|
||||||
@ -101,7 +104,7 @@ type ProtocolManager struct {
|
|||||||
|
|
||||||
// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
||||||
// with the Ethereum network.
|
// with the Ethereum network.
|
||||||
func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) {
|
func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash, rollupBuilder *rollup.TransitionBatchBuilder) (*ProtocolManager, error) {
|
||||||
// Create the protocol manager with the base fields
|
// Create the protocol manager with the base fields
|
||||||
manager := &ProtocolManager{
|
manager := &ProtocolManager{
|
||||||
networkID: networkID,
|
networkID: networkID,
|
||||||
@ -115,6 +118,7 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh
|
|||||||
noMorePeers: make(chan struct{}),
|
noMorePeers: make(chan struct{}),
|
||||||
txsyncCh: make(chan *txsync),
|
txsyncCh: make(chan *txsync),
|
||||||
quitSync: make(chan struct{}),
|
quitSync: make(chan struct{}),
|
||||||
|
rollupBlockBuilder: rollupBuilder,
|
||||||
}
|
}
|
||||||
if mode == downloader.FullSync {
|
if mode == downloader.FullSync {
|
||||||
// The database seems empty as the current block is the genesis. Yet the fast
|
// The database seems empty as the current block is the genesis. Yet the fast
|
||||||
@ -267,6 +271,8 @@ func (pm *ProtocolManager) Stop() {
|
|||||||
pm.txsSub.Unsubscribe() // quits txBroadcastLoop
|
pm.txsSub.Unsubscribe() // quits txBroadcastLoop
|
||||||
pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
|
pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
|
||||||
|
|
||||||
|
pm.rollupBlockBuilder.Stop()
|
||||||
|
|
||||||
// Quit the sync loop.
|
// Quit the sync loop.
|
||||||
// After this send has completed, no new peers will be accepted.
|
// After this send has completed, no new peers will be accepted.
|
||||||
pm.noMorePeers <- struct{}{}
|
pm.noMorePeers <- struct{}{}
|
||||||
@ -815,6 +821,7 @@ func (pm *ProtocolManager) minedBroadcastLoop() {
|
|||||||
if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
|
if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
|
||||||
pm.BroadcastBlock(ev.Block, true) // First propagate block to peers
|
pm.BroadcastBlock(ev.Block, true) // First propagate block to peers
|
||||||
pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest
|
pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest
|
||||||
|
pm.rollupBlockBuilder.NewBlock(ev.Block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package eth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/rollup"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -495,7 +496,12 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create new blockchain: %v", err)
|
t.Fatalf("failed to create new blockchain: %v", err)
|
||||||
}
|
}
|
||||||
pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), ethash.NewFaker(), blockchain, db, 1, nil)
|
blockSubmitter := rollup.NewBlockSubmitter()
|
||||||
|
rollupBlockBuilder, err := rollup.NewTransitionBatchBuilder(db, blockchain, blockSubmitter, 5 * time.Minute, 9_000_000_000, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create Rollup Block Builder: %v", err)
|
||||||
|
}
|
||||||
|
pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), ethash.NewFaker(), blockchain, db, 1, nil, rollupBlockBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||||
}
|
}
|
||||||
@ -582,7 +588,12 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create new blockchain: %v", err)
|
t.Fatalf("failed to create new blockchain: %v", err)
|
||||||
}
|
}
|
||||||
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, 1, nil)
|
blockSubmitter := rollup.NewBlockSubmitter()
|
||||||
|
rollupBlockBuilder, err := rollup.NewTransitionBatchBuilder(db, blockchain, blockSubmitter, 5 * time.Minute, 9_000_000_000, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create Rollup Block Builder: %v", err)
|
||||||
|
}
|
||||||
|
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, 1, nil, rollupBlockBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||||
}
|
}
|
||||||
@ -650,7 +661,12 @@ func TestBroadcastMalformedBlock(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create new blockchain: %v", err)
|
t.Fatalf("failed to create new blockchain: %v", err)
|
||||||
}
|
}
|
||||||
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil)
|
blockSubmitter := rollup.NewBlockSubmitter()
|
||||||
|
rollupBlockBuilder, err := rollup.NewTransitionBatchBuilder(db, blockchain, blockSubmitter, 5 * time.Minute, 9_000_000_000, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create Rollup Block Builder: %v", err)
|
||||||
|
}
|
||||||
|
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil, rollupBlockBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,12 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/rollup"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
@ -68,7 +70,12 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
|
|||||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db, 1, nil)
|
blockSubmitter := rollup.NewBlockSubmitter()
|
||||||
|
rollupBlockBuilder, err := rollup.NewTransitionBatchBuilder(db, blockchain, blockSubmitter, 5 * time.Minute, 9_000_000_000, 200)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to create Rollup Block Builder: %v", err)
|
||||||
|
}
|
||||||
|
pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db, 1, nil, rollupBlockBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package eth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/rollup"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@ -180,8 +181,14 @@ func TestForkIDSplit(t *testing.T) {
|
|||||||
blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
|
blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
|
||||||
blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
|
blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
|
||||||
|
|
||||||
ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), new(testTxPool), engine, chainNoFork, dbNoFork, 1, nil)
|
blockSubmitterNoFork = rollup.NewBlockSubmitter()
|
||||||
ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), new(testTxPool), engine, chainProFork, dbProFork, 1, nil)
|
blockSubmitterProFork = rollup.NewBlockSubmitter()
|
||||||
|
|
||||||
|
rollupBlockBuilderNoFork, _ = rollup.NewTransitionBatchBuilder(dbNoFork, chainNoFork, blockSubmitterNoFork, 5 * time.Minute, 9_000_000_000, 200)
|
||||||
|
rollupBlockBuilderProFork, _ = rollup.NewTransitionBatchBuilder(dbProFork, chainProFork, blockSubmitterProFork, 5 * time.Minute, 9_000_000_000, 200)
|
||||||
|
|
||||||
|
ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), new(testTxPool), engine, chainNoFork, dbNoFork, 1, nil, rollupBlockBuilderNoFork)
|
||||||
|
ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), new(testTxPool), engine, chainProFork, dbProFork, 1, nil, rollupBlockBuilderProFork)
|
||||||
)
|
)
|
||||||
ethNoFork.Start(1000)
|
ethNoFork.Start(1000)
|
||||||
ethProFork.Start(1000)
|
ethProFork.Start(1000)
|
||||||
|
@ -696,6 +696,10 @@ func (w *worker) updateSnapshot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
|
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
|
||||||
|
// Make sure there's only one tx per block
|
||||||
|
if w.current != nil && len(w.current.txs) > 0 {
|
||||||
|
return nil, core.ErrGasLimitReached
|
||||||
|
}
|
||||||
snap := w.current.state.Snapshot()
|
snap := w.current.state.Snapshot()
|
||||||
|
|
||||||
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig())
|
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig())
|
||||||
|
342
rollup/transition_batch_builder.go
Normal file
342
rollup/transition_batch_builder.go
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
package rollup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New(TransitionBatchBuilder{})
|
||||||
|
ErrTransactionLimitReached = errors.New("transaction limit reached")
|
||||||
|
ErrMoreThanOneTxInBlock = errors.New("block contains more than one transaction")
|
||||||
|
LastProcessedDBKey = []byte("lastProcessedRollupBlock")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActiveBatch struct {
|
||||||
|
firstBlockNumber uint64
|
||||||
|
lastBlockNumber uint64
|
||||||
|
gasUsed uint64
|
||||||
|
|
||||||
|
transitionBatch *TransitionBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActiveBatch(defaultTxCapacity int) *ActiveBatch {
|
||||||
|
return &ActiveBatch{
|
||||||
|
firstBlockNumber: 0,
|
||||||
|
lastBlockNumber: 0,
|
||||||
|
gasUsed: TransitionBatchGasBuffer,
|
||||||
|
transitionBatch: NewTransitionBatch(defaultTxCapacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addBlock adds a Geth Block to the ActiveBatch in question, only if it fits.
|
||||||
|
// Cases in which it would not fit are if it would put the block above the configured
|
||||||
|
// max number of transactions or max block gas, resulting in
|
||||||
|
// ErrTransactionLimitReached and core.ErrGasLimitReached, respectively.
|
||||||
|
func (b *ActiveBatch) addBlock(block *types.Block, maxBlockGas uint64, maxBlockTransactions int) error {
|
||||||
|
if maxBlockTransactions < len(b.transitionBatch.transitions)+1 {
|
||||||
|
return ErrTransactionLimitReached
|
||||||
|
}
|
||||||
|
blockGasCost := GetBlockRollupGasUsage(block)
|
||||||
|
if maxBlockGas < b.gasUsed+blockGasCost {
|
||||||
|
return core.ErrGasLimitReached
|
||||||
|
}
|
||||||
|
|
||||||
|
b.transitionBatch.addBlock(block)
|
||||||
|
b.gasUsed += blockGasCost
|
||||||
|
if b.firstBlockNumber == 0 {
|
||||||
|
b.firstBlockNumber = block.NumberU64()
|
||||||
|
}
|
||||||
|
b.lastBlockNumber = block.NumberU64()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransitionBatchBuilder struct {
|
||||||
|
db ethdb.Database
|
||||||
|
blockProvider BlockStore
|
||||||
|
rollupBatchSubmitter RollupTransitionBatchSubmitter
|
||||||
|
pendingMu sync.RWMutex
|
||||||
|
|
||||||
|
newBlockCh chan *types.Block
|
||||||
|
|
||||||
|
maxTransitionBatchTime time.Duration
|
||||||
|
maxTransitionBatchGas uint64
|
||||||
|
maxTransitionBatchTransactions int
|
||||||
|
|
||||||
|
lastProcessedBlockNumber uint64
|
||||||
|
activeBatch *ActiveBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransitionBatchBuilder(db ethdb.Database, blockStore interface{}, rollupBlockSubmitter interface{}, maxBlockTime time.Duration, maxBlockGas uint64, maxBlockTransactions int) (*TransitionBatchBuilder, error) {
|
||||||
|
lastBlock, err := fetchLastProcessedBlockNumber(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := &TransitionBatchBuilder{
|
||||||
|
db: db,
|
||||||
|
blockProvider: blockStore.(BlockStore),
|
||||||
|
rollupBatchSubmitter: rollupBlockSubmitter.(RollupTransitionBatchSubmitter),
|
||||||
|
newBlockCh: make(chan *types.Block, 10_000),
|
||||||
|
|
||||||
|
maxTransitionBatchTime: maxBlockTime,
|
||||||
|
maxTransitionBatchGas: maxBlockGas,
|
||||||
|
maxTransitionBatchTransactions: maxBlockTransactions,
|
||||||
|
|
||||||
|
lastProcessedBlockNumber: lastBlock,
|
||||||
|
activeBatch: newActiveBatch(maxBlockTransactions),
|
||||||
|
}
|
||||||
|
|
||||||
|
go builder.buildLoop(maxBlockTime)
|
||||||
|
|
||||||
|
return builder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlock handles new blocks from Geth by adding them to the newBlockCh channel
|
||||||
|
// for processing and returning so as to not delay the caller.
|
||||||
|
func (b *TransitionBatchBuilder) NewBlock(block *types.Block) {
|
||||||
|
b.newBlockCh <- block
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop handles graceful shutdown of the TransitionBatchBuilder.
|
||||||
|
func (b *TransitionBatchBuilder) Stop() {
|
||||||
|
close(b.newBlockCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildLoop initiates TransitionBatch production and submission either based on
|
||||||
|
// a new Geth Block being received or the maxBlockTime being reached.
|
||||||
|
func (b *TransitionBatchBuilder) buildLoop(maxBlockTime time.Duration) {
|
||||||
|
lastProcessed := b.lastProcessedBlockNumber
|
||||||
|
|
||||||
|
if err := b.sync(); err != nil {
|
||||||
|
panic(fmt.Errorf("error syncing: %+v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(maxBlockTime)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case block, ok := <-b.newBlockCh:
|
||||||
|
if !ok {
|
||||||
|
timer.Stop()
|
||||||
|
logger.Info("Closing transition batch builder new block channel. If not shutting down, this is an error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
built, err := b.handleNewBlock(block)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("error handling new block. Error: %v. Block: %+v", err, block))
|
||||||
|
}
|
||||||
|
if timer != nil && built {
|
||||||
|
timer.Reset(b.maxTransitionBatchTime)
|
||||||
|
}
|
||||||
|
case <-timer.C:
|
||||||
|
if lastProcessed != b.lastProcessedBlockNumber && b.activeBatch.firstBlockNumber != 0 {
|
||||||
|
if _, err := b.buildRollupBlock(true); err != nil {
|
||||||
|
panic(fmt.Errorf("error buidling block: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastProcessed = b.lastProcessedBlockNumber
|
||||||
|
timer.Reset(maxBlockTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleNewBlock processes a newly received Geth Block, ignoring old / future blocks
|
||||||
|
// and building and submitting TransitionBatches if the pending TransitionBatch is full.
|
||||||
|
func (b *TransitionBatchBuilder) handleNewBlock(block *types.Block) (bool, error) {
|
||||||
|
logger.Debug("handling new block in transition batch builder", "block", block)
|
||||||
|
if block.NumberU64() <= b.lastProcessedBlockNumber {
|
||||||
|
logger.Debug("handling old block -- ignoring", "block", block)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if block.NumberU64() > b.lastProcessedBlockNumber+1 {
|
||||||
|
logger.Error("received future block", "block", block, "expectedNumber", b.lastProcessedBlockNumber+1)
|
||||||
|
// TODO: add to queue and/or try to fetch blocks in between.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if txCount := len(block.Transactions()); txCount > 1 {
|
||||||
|
// should never happen
|
||||||
|
logger.Error("received block with more than one transaction", "block", block)
|
||||||
|
return false, ErrMoreThanOneTxInBlock
|
||||||
|
} else if txCount == 0 {
|
||||||
|
logger.Debug("handling empty block -- ignoring", "block", block)
|
||||||
|
b.lastProcessedBlockNumber = block.NumberU64()
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err := b.addBlock(block); err {
|
||||||
|
case core.ErrGasLimitReached, ErrTransactionLimitReached:
|
||||||
|
if _, e := b.buildRollupBlock(false); e != nil {
|
||||||
|
logger.Error("unable to build transition batch", "error", e, "transition batch", b.activeBatch)
|
||||||
|
return false, e
|
||||||
|
}
|
||||||
|
if addErr := b.addBlock(block); addErr != nil {
|
||||||
|
// TODO: Retry and whatnot instead of instant panic
|
||||||
|
logger.Error("unable to build transition batch", "error", addErr, "transition batch", b.activeBatch)
|
||||||
|
return false, addErr
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("unrecognized error adding to transition batch in progress", "error", err, "transition batch", b.activeBatch)
|
||||||
|
return false, err
|
||||||
|
} else {
|
||||||
|
logger.Debug("successfully added block to transition batch in progress", "number", block.NumberU64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
built, err := b.tryBuildRollupBlock()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error building block", "error", err, "block", block)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return built, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync catches the TransitionBatchBuilder up to the Geth chain by fetching all Geth Blocks between
|
||||||
|
// its last processed Block and the current Block, building and submitting RollupBlocks if/when
|
||||||
|
// they are full.
|
||||||
|
func (b *TransitionBatchBuilder) sync() error {
|
||||||
|
logger.Info("syncing blocks in transition batch builder", "starting block", b.lastProcessedBlockNumber)
|
||||||
|
|
||||||
|
for {
|
||||||
|
blockNum := b.lastProcessedBlockNumber + uint64(1)
|
||||||
|
block := b.blockProvider.GetBlockByNumber(blockNum)
|
||||||
|
logger.Info("got block number", "number", blockNum, "block", block)
|
||||||
|
if block == nil {
|
||||||
|
logger.Info("done syncing blocks in transition batch builder", "number", b.lastProcessedBlockNumber)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := b.handleNewBlock(block); err != nil {
|
||||||
|
logger.Error("Error handling new block", "error", err)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
logger.Debug("successfully synced block", "number", blockNum, "last processed", b.lastProcessedBlockNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addBlock adds a Geth Block to the TransitionBatch if it fits. If not, it will return an error.
|
||||||
|
func (b *TransitionBatchBuilder) addBlock(block *types.Block) error {
|
||||||
|
b.pendingMu.Lock()
|
||||||
|
defer b.pendingMu.Unlock()
|
||||||
|
if err := b.activeBatch.addBlock(block, b.maxTransitionBatchGas, b.maxTransitionBatchTransactions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.lastProcessedBlockNumber = block.NumberU64()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryBuildRollupBlock builds and submits a TransitionBatch if the pending TransitionBatch is full.
|
||||||
|
func (b *TransitionBatchBuilder) tryBuildRollupBlock() (bool, error) {
|
||||||
|
txCount := len(b.activeBatch.transitionBatch.transitions)
|
||||||
|
gasAfterOneMoreTx := b.activeBatch.gasUsed + MinTxGas
|
||||||
|
if txCount < b.maxTransitionBatchTransactions && gasAfterOneMoreTx <= b.maxTransitionBatchGas {
|
||||||
|
logger.Debug("transition batch is not full, so not finalizing it", "txCount", txCount, "gasAfterOneMoreTx", gasAfterOneMoreTx)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
logger.Debug("transition batch is full, finalizing it", "txCount", txCount, "gasAfterOneMoreTx", gasAfterOneMoreTx)
|
||||||
|
|
||||||
|
return b.buildRollupBlock(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRollupBlock builds a TransitionBatch if the pending TransitionBatch is full or if force is true
|
||||||
|
// and the pending TransitionBatch is not empty.
|
||||||
|
func (b *TransitionBatchBuilder) buildRollupBlock(force bool) (bool, error) {
|
||||||
|
var toSubmit *ActiveBatch
|
||||||
|
b.pendingMu.Lock()
|
||||||
|
defer b.pendingMu.Unlock()
|
||||||
|
|
||||||
|
txCount := len(b.activeBatch.transitionBatch.transitions)
|
||||||
|
|
||||||
|
if force && txCount == 0 {
|
||||||
|
logger.Debug("transition batch is empty so not finalizing it, even though force = true")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if !force && txCount < b.maxTransitionBatchTransactions && b.activeBatch.gasUsed+MinTxGas <= b.maxTransitionBatchGas {
|
||||||
|
logger.Debug("transition batch is not full, so not finalizing it")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
logger.Debug("building transition batch")
|
||||||
|
|
||||||
|
toSubmit = b.activeBatch
|
||||||
|
b.activeBatch = newActiveBatch(b.maxTransitionBatchTransactions)
|
||||||
|
|
||||||
|
if err := b.submitBlock(toSubmit); err != nil {
|
||||||
|
logger.Error("error submitting transition batch", "lastBlockNumber", toSubmit.lastBlockNumber, "error", err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
logger.Debug("successfully built transition batch", "lastBlockNumber", toSubmit.lastBlockNumber)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// submitBlock submits a TransitionBatch to the RollupTransitionBatchSubmitter and updates the DB
|
||||||
|
// to indicate the last processed Geth Block included in the TransitionBatch.
|
||||||
|
func (b *TransitionBatchBuilder) submitBlock(block *ActiveBatch) error {
|
||||||
|
// TODO: Submit to chain & get hash
|
||||||
|
logger.Debug("submitting transition batch", "block", block)
|
||||||
|
|
||||||
|
if err := b.rollupBatchSubmitter.submit(block.transitionBatch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.db.Put(LastProcessedDBKey, SerializeBlockNumber(block.lastBlockNumber)); err != nil {
|
||||||
|
logger.Error("error saving last processed transition batch", "block", block)
|
||||||
|
// TODO: Something here
|
||||||
|
}
|
||||||
|
logger.Debug("transition batch submitted", "block", block)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchLastProcessedBlockNumber fetches the last processed Geth Block # from the DB.
|
||||||
|
func fetchLastProcessedBlockNumber(db ethdb.Database) (uint64, error) {
|
||||||
|
has, err := db.Has(LastProcessedDBKey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("received error checking if LastProcessedDBKey exists in DB", "error", err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
lastProcessedBytes, e := db.Get(LastProcessedDBKey)
|
||||||
|
if e != nil {
|
||||||
|
logger.Error("error fetching LastProcessedDBKey from DB", "error", err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
lastProcessedBlock := DeserializeBlockNumber(lastProcessedBytes)
|
||||||
|
logger.Info("fetched last processed block from database", "number", lastProcessedBlock)
|
||||||
|
return lastProcessedBlock, nil
|
||||||
|
} else {
|
||||||
|
logger.Info("no last processed block found in the db -- returning 0")
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeBlockNumber serializes the number for DB storage
|
||||||
|
func SerializeBlockNumber(blockNumber uint64) []byte {
|
||||||
|
numberAsByteArray := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(numberAsByteArray, blockNumber)
|
||||||
|
return numberAsByteArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeserializeBlockNumber deserializes the number from DB storage
|
||||||
|
func DeserializeBlockNumber(blockNumber []byte) uint64 {
|
||||||
|
return binary.LittleEndian.Uint64(blockNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockRollupGasUsage determines the amount of L1 gas the provided Geth Block will use
|
||||||
|
// when submitted to mainnet.
|
||||||
|
func GetBlockRollupGasUsage(block *types.Block) uint64 {
|
||||||
|
return params.SstoreSetGas + uint64(len(block.Transactions()[0].Data()))*params.TxDataNonZeroGasEIP2028
|
||||||
|
}
|
491
rollup/transition_batch_builder_test.go
Normal file
491
rollup/transition_batch_builder_test.go
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
package rollup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
timeoutDuration = time.Millisecond * 100
|
||||||
|
|
||||||
|
testTxPoolConfig core.TxPoolConfig
|
||||||
|
cliqueChainConfig *params.ChainConfig
|
||||||
|
|
||||||
|
// Test accounts
|
||||||
|
testBankKey, _ = crypto.GenerateKey()
|
||||||
|
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||||
|
testBankFunds = big.NewInt(1000000000000000000)
|
||||||
|
|
||||||
|
testUserKey, _ = crypto.GenerateKey()
|
||||||
|
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cliqueChainConfig = params.AllCliqueProtocolChanges
|
||||||
|
cliqueChainConfig.Clique = ¶ms.CliqueConfig{
|
||||||
|
Period: 10,
|
||||||
|
Epoch: 30000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestBlockStore struct {
|
||||||
|
blocks map[uint64]*types.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestBlockStore(blocks []*types.Block) *TestBlockStore {
|
||||||
|
store := &TestBlockStore{blocks: make(map[uint64]*types.Block, len(blocks))}
|
||||||
|
for _, block := range blocks {
|
||||||
|
store.blocks[block.NumberU64()] = block
|
||||||
|
}
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestBlockStore) GetBlockByNumber(number uint64) *types.Block {
|
||||||
|
if block, found := t.blocks[number]; found {
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestTransitionBatchSubmitter struct {
|
||||||
|
submittedTransitions []*TransitionBatch
|
||||||
|
submitCh chan *TransitionBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestBlockSubmitter(submittedBlocks []*TransitionBatch, submitCh chan *TransitionBatch) *TestTransitionBatchSubmitter {
|
||||||
|
return &TestTransitionBatchSubmitter{
|
||||||
|
submittedTransitions: submittedBlocks,
|
||||||
|
submitCh: submitCh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestTransitionBatchSubmitter) submit(block *TransitionBatch) error {
|
||||||
|
t.submittedTransitions = append(t.submittedTransitions, block)
|
||||||
|
t.submitCh <- block
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBlocks(number int, startIndex int, withTx bool) types.Blocks {
|
||||||
|
blocks := make(types.Blocks, number)
|
||||||
|
for i := 0; i < number; i++ {
|
||||||
|
header := &types.Header{Number: big.NewInt(int64(i + startIndex))}
|
||||||
|
txs := make(types.Transactions, 0)
|
||||||
|
if withTx {
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(uint64(i), testUserAddress, big.NewInt(1), params.TxGas, big.NewInt(0), nil), types.HomesteadSigner{}, testBankKey)
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
block := types.NewBlock(header, txs, make([]*types.Header, 0), make([]*types.Receipt, 0))
|
||||||
|
blocks[i] = block
|
||||||
|
}
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertTransitionFromBlock(t *testing.T, transition *Transition, block *types.Block) {
|
||||||
|
if transition.postState != block.Root() {
|
||||||
|
t.Fatal("expecting transitionBatch postState to equal block root", "postState", transition.postState, "block.Hash()", block.Root())
|
||||||
|
}
|
||||||
|
if transition.transaction.Hash() != block.Transactions()[0].Hash() {
|
||||||
|
t.Fatal("expecting transitionBatch tx hash to equal block tx hash", "transitionBatch tx", transition.transaction.Hash(), "block tx", block.Transactions()[0].Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestTransitionBatchBuilder(blockStore *TestBlockStore, batchSubmitter *TestTransitionBatchSubmitter, lastProcessedBlock uint64, maxBlockTime time.Duration, maxBlockGas uint64, maxBlockTransactions int) (*TransitionBatchBuilder, error) {
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
|
||||||
|
if lastProcessedBlock != 0 {
|
||||||
|
if err := db.Put(LastProcessedDBKey, SerializeBlockNumber(lastProcessedBlock)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewTransitionBatchBuilder(db, blockStore, batchSubmitter, maxBlockTime, maxBlockGas, maxBlockTransactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSubmitChBlockStoreAndSubmitter() (chan *TransitionBatch, *TestBlockStore, *TestTransitionBatchSubmitter) {
|
||||||
|
submitCh := make(chan *TransitionBatch, 10)
|
||||||
|
return submitCh, newTestBlockStore(make([]*types.Block, 0)), newTestBlockSubmitter(make([]*TransitionBatch, 0), submitCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************
|
||||||
|
* Tests Start *
|
||||||
|
***************/
|
||||||
|
|
||||||
|
/********************
|
||||||
|
* Submission Tests *
|
||||||
|
********************/
|
||||||
|
|
||||||
|
// Single block submission tests
|
||||||
|
|
||||||
|
func TestBatchSubmissionMaxTransactions(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(1, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
if len(batchSubmitter.submittedTransitions) > 1 {
|
||||||
|
t.Fatal("Expected 1 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockLessThanMaxTransactions(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, 1_000_000_000, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(1, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case <-batchSubmitCh:
|
||||||
|
t.Fatalf("should not have submitted a block")
|
||||||
|
case <-timeout:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchSubmissionMaxGas(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
|
||||||
|
blocks := createBlocks(1, 1, true)
|
||||||
|
gasLimit := GetBlockRollupGasUsage(blocks[0]) + TransitionBatchGasBuffer
|
||||||
|
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, gasLimit, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
if len(batchSubmitter.submittedTransitions) > 1 {
|
||||||
|
t.Fatal("Expected 1 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockLessThanMaxGas(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
|
||||||
|
blocks := createBlocks(1, 1, true)
|
||||||
|
gasLimit := GetBlockRollupGasUsage(blocks[0]) + TransitionBatchGasBuffer + MinTxGas
|
||||||
|
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, gasLimit, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case <-batchSubmitCh:
|
||||||
|
t.Fatalf("should not have submitted a block")
|
||||||
|
case <-timeout:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple block submission tests
|
||||||
|
|
||||||
|
func TestMultipleBatchSubmissionMaxTransactions(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
time.Sleep(time.Microsecond * 10)
|
||||||
|
if len(batchSubmitter.submittedTransitions) != 2 {
|
||||||
|
t.Fatal("Expected 2 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
assertTransitionFromBlock(t, batchSubmitter.submittedTransitions[1].transitions[0], blocks[1])
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleBlocksLessThanMaxTransactions(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, 1_000_000_000, 3)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case <-batchSubmitCh:
|
||||||
|
t.Fatalf("should not have submitted a block")
|
||||||
|
case <-timeout:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleBatchSubmissionMaxGas(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, true)
|
||||||
|
gasLimit := GetBlockRollupGasUsage(blocks[0]) + TransitionBatchGasBuffer
|
||||||
|
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, gasLimit, 3)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
time.Sleep(time.Microsecond * 10)
|
||||||
|
if len(batchSubmitter.submittedTransitions) != 2 {
|
||||||
|
t.Fatal("Expected 2 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
assertTransitionFromBlock(t, batchSubmitter.submittedTransitions[1].transitions[0], blocks[1])
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleBlocksLessThanMaxGas(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, true)
|
||||||
|
gasLimit := 2 * (GetBlockRollupGasUsage(blocks[0]) + TransitionBatchGasBuffer + MinTxGas)
|
||||||
|
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, gasLimit, 3)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case <-batchSubmitCh:
|
||||||
|
t.Fatalf("should not have submitted a block")
|
||||||
|
case <-timeout:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty block tests
|
||||||
|
|
||||||
|
func TestEmptyBlocksIgnored(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, false)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case <-batchSubmitCh:
|
||||||
|
t.Fatalf("should not have submitted a block")
|
||||||
|
case <-timeout:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyBlocksIgnoredWithNonEmpty(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyBlocks := createBlocks(2, 1, false)
|
||||||
|
|
||||||
|
blockBuilder.NewBlock(emptyBlocks[0])
|
||||||
|
blockBuilder.NewBlock(emptyBlocks[1])
|
||||||
|
|
||||||
|
nonEmpty := createBlocks(1, 3, true)[0]
|
||||||
|
blockBuilder.NewBlock(nonEmpty)
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], nonEmpty)
|
||||||
|
if len(batchSubmitter.submittedTransitions) > 1 {
|
||||||
|
t.Fatal("Expected 1 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer submission
|
||||||
|
|
||||||
|
func TestBatchSubmissionMaxTimeBetweenBlocks(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Microsecond*1, 1_000_000_000, 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
time.Sleep(time.Microsecond * 10)
|
||||||
|
if len(batchSubmitter.submittedTransitions) != 2 && len(transitionBatch.transitions) != 2 {
|
||||||
|
t.Fatal("Expected 2 transitions to have been submitted", "blocksSubmitted", len(batchSubmitter.submittedTransitions), "transitionsInFirst", len(transitionBatch.transitions))
|
||||||
|
}
|
||||||
|
var secondTransition *Transition
|
||||||
|
switch true {
|
||||||
|
case len(batchSubmitter.submittedTransitions) == 2:
|
||||||
|
secondTransition = batchSubmitter.submittedTransitions[1].transitions[0]
|
||||||
|
case len(transitionBatch.transitions) == 2:
|
||||||
|
secondTransition = transitionBatch.transitions[1]
|
||||||
|
}
|
||||||
|
assertTransitionFromBlock(t, secondTransition, blocks[1])
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchSubmissionMaxTimeBetweenBlocksReset(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 0, time.Microsecond*1, 1_000_000_000, 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(2, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
if len(batchSubmitter.submittedTransitions) != 1 {
|
||||||
|
t.Fatal("Expected 1 batch to have been submitted", "blocksSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBuilder.NewBlock(blocks[1])
|
||||||
|
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[1])
|
||||||
|
if len(batchSubmitter.submittedTransitions) != 2 {
|
||||||
|
t.Fatal("Expected 2 batches to have been submitted", "blocksSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************
|
||||||
|
* Existing Data Tests *
|
||||||
|
***********************/
|
||||||
|
|
||||||
|
func TestBatchSubmissionWithExistingData(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 1, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(1, 2, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], blocks[0])
|
||||||
|
if len(batchSubmitter.submittedTransitions) > 1 {
|
||||||
|
t.Fatal("Expected 1 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchSubmissionWithExistingDataNoRepeats(t *testing.T) {
|
||||||
|
batchSubmitCh, blockStore, batchSubmitter := getSubmitChBlockStoreAndSubmitter()
|
||||||
|
blockBuilder, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 1, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks := createBlocks(1, 1, true)
|
||||||
|
blockBuilder.NewBlock(blocks[0])
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case <-batchSubmitCh:
|
||||||
|
t.Fatalf("block should not have been submitted")
|
||||||
|
case <-timeout:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchSubmissionWithExistingDataNewBlocks(t *testing.T) {
|
||||||
|
existingBlocks := createBlocks(2, 1, true)
|
||||||
|
|
||||||
|
batchSubmitCh := make(chan *TransitionBatch, 10)
|
||||||
|
blockStore, batchSubmitter := newTestBlockStore(existingBlocks), newTestBlockSubmitter(make([]*TransitionBatch, 0), batchSubmitCh)
|
||||||
|
|
||||||
|
_, err := newTestTransitionBatchBuilder(blockStore, batchSubmitter, 1, time.Minute*1, 1_000_000_000, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make test batch builder, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.After(timeoutDuration)
|
||||||
|
select {
|
||||||
|
case transitionBatch := <-batchSubmitCh:
|
||||||
|
assertTransitionFromBlock(t, transitionBatch.transitions[0], existingBlocks[1])
|
||||||
|
if len(batchSubmitter.submittedTransitions) != 1 {
|
||||||
|
t.Fatal("Expected 1 batch to have been submitted", "numSubmitted", len(batchSubmitter.submittedTransitions))
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatalf("test timeout")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
14
rollup/transition_batch_submitter.go
Normal file
14
rollup/transition_batch_submitter.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package rollup
|
||||||
|
|
||||||
|
type RollupTransitionBatchSubmitter interface {
|
||||||
|
submit(block *TransitionBatch) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransitionBatchSubmitter struct{}
|
||||||
|
|
||||||
|
func NewBlockSubmitter() *TransitionBatchSubmitter {
|
||||||
|
return &TransitionBatchSubmitter{}
|
||||||
|
}
|
||||||
|
func (d *TransitionBatchSubmitter) submit(block *TransitionBatch) error {
|
||||||
|
return nil
|
||||||
|
}
|
42
rollup/types.go
Normal file
42
rollup/types.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package rollup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MinTxBytes = uint64(100)
|
||||||
|
MinTxGas = MinTxBytes*params.TxDataNonZeroGasEIP2028 + params.SstoreSetGas
|
||||||
|
TransitionBatchGasBuffer = uint64(1_000_000)
|
||||||
|
)
|
||||||
|
|
||||||
|
type BlockStore interface {
|
||||||
|
GetBlockByNumber(number uint64) *types.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transition struct {
|
||||||
|
transaction *types.Transaction
|
||||||
|
postState common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransition(tx *types.Transaction, postState common.Hash) *Transition {
|
||||||
|
return &Transition{
|
||||||
|
transaction: tx,
|
||||||
|
postState: postState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransitionBatch struct {
|
||||||
|
transitions []*Transition
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransitionBatch(defaultSize int) *TransitionBatch {
|
||||||
|
return &TransitionBatch{transitions: make([]*Transition, 0, defaultSize)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addBlock adds a Geth Block to the TransitionBatch. This is just its transaction and state root.
|
||||||
|
func (r *TransitionBatch) addBlock(block *types.Block) {
|
||||||
|
r.transitions = append(r.transitions, newTransition(block.Transactions()[0], block.Root()))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user