diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go new file mode 100644 index 000000000000..a26cadd4e4c2 --- /dev/null +++ b/arbitrum/apibackend.go @@ -0,0 +1,256 @@ +package arbitrum + +import ( + "context" + "errors" + "math/big" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +type APIBackend struct { + b *Backend +} + +func createRegisterAPIBackend(backend *Backend) { + backend.apiBackend = &APIBackend{ + b: backend, + } + backend.stack.RegisterAPIs(backend.apiBackend.GetAPIs()) +} + +func (a *APIBackend) GetAPIs() []rpc.API { + apis := ethapi.GetAPIs(a) + + apis = append(apis, rpc.API{ + Namespace: "eth", + Version: "1.0", + Service: filters.NewPublicFilterAPI(a, false, 5*time.Minute), + Public: true, + }) + return apis +} + +// General Ethereum API +func (a *APIBackend) SyncProgress() ethereum.SyncProgress { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) ChainDb() ethdb.Database { + return a.b.ethDatabase +} + +func (a *APIBackend) AccountManager() *accounts.Manager { + return a.b.stack.AccountManager() +} + +func (a *APIBackend) ExtRPCEnabled() bool { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) RPCGasCap() uint64 { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) RPCTxFeeCap() float64 { + return a.b.ethConfig.RPCTxFeeCap +} + +func (a *APIBackend) UnprotectedAllowed() bool { + return true // TODO: is that true? +} + +// Blockchain API +func (a *APIBackend) SetHead(number uint64) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number == rpc.LatestBlockNumber { + return a.b.blockChain.CurrentBlock().Header(), nil + } + return a.b.blockChain.GetHeaderByNumber(uint64(number.Int64())), nil +} + +func (a *APIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return a.b.blockChain.GetHeaderByHash(hash), nil +} + +func (a *APIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { + number, isnum := blockNrOrHash.Number() + if isnum { + return a.HeaderByNumber(ctx, number) + } + hash, ishash := blockNrOrHash.Hash() + if ishash { + return a.HeaderByHash(ctx, hash) + } + return nil, errors.New("invalid arguments; neither block nor hash specified") +} + +func (a *APIBackend) CurrentHeader() *types.Header { + return a.b.blockChain.CurrentHeader() +} + +func (a *APIBackend) CurrentBlock() *types.Block { + return a.b.blockChain.CurrentBlock() +} + +func (a *APIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + return a.b.blockChain.GetBlockByNumber(uint64(number.Int64())), nil +} + +func (a *APIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return a.b.blockChain.GetBlockByHash(hash), nil +} + +func (a *APIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { + number, isnum := blockNrOrHash.Number() + if isnum { + return a.BlockByNumber(ctx, number) + } + hash, ishash := blockNrOrHash.Hash() + if ishash { + return a.BlockByHash(ctx, hash) + } + return nil, errors.New("invalid arguments; neither block nor hash specified") +} + +func (a *APIBackend) stateAndHeaderFromHeader(header *types.Header, err error) (*state.StateDB, *types.Header, error) { + if err != nil { + return nil, header, err + } + if header == nil { + return nil, nil, errors.New("header not found") + } + state, err := a.b.blockChain.StateAt(header.Root) + return state, header, err +} + +func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { + return a.stateAndHeaderFromHeader(a.HeaderByNumber(ctx, number)) +} + +func (a *APIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { + return a.stateAndHeaderFromHeader(a.HeaderByNumberOrHash(ctx, blockNrOrHash)) +} + +func (a *APIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + return a.b.blockChain.GetReceiptsByHash(hash), nil +} + +func (a *APIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return a.b.blockChain.SubscribeChainEvent(ch) +} + +func (a *APIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return a.b.blockChain.SubscribeChainHeadEvent(ch) +} + +func (a *APIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { + return a.b.blockChain.SubscribeChainSideEvent(ch) +} + +// Transaction pool API +func (a *APIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + return a.b.EnqueueL2Message(signedTx) +} + +func (a *APIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(a.b.ethDatabase, txHash) + return tx, blockHash, blockNumber, index, nil +} + +func (a *APIBackend) GetPoolTransactions() (types.Transactions, error) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) Stats() (pending int, queued int) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return a.b.SubscribeNewTxsEvent(ch) +} + +// Filter API +func (a *APIBackend) BloomStatus() (uint64, uint64) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { + panic("not implemented") // TODO: Implement +} + +func (a *APIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return a.b.blockChain.SubscribeLogsEvent(ch) +} + +func (a *APIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { + //Arbitrum doesn't really need pending logs. Logs are published as soon as we know them.. + return a.SubscribeLogsEvent(ch) +} + +func (a *APIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + return a.b.blockChain.SubscribeRemovedLogsEvent(ch) +} + +func (a *APIBackend) ChainConfig() *params.ChainConfig { + return a.b.blockChain.Config() +} + +func (a *APIBackend) Engine() consensus.Engine { + return a.b.blockChain.Engine() +} diff --git a/arbitrum/arbos_interface.go b/arbitrum/arbos_interface.go new file mode 100644 index 000000000000..4d486116dba4 --- /dev/null +++ b/arbitrum/arbos_interface.go @@ -0,0 +1,12 @@ +package arbitrum + +import ( + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" +) + +type ArbosWrapper interface { + BuildBlock(force bool) (*types.Block, types.Receipts, *state.StateDB) + + EnqueueSequencerTx(tx *types.Transaction) error +} diff --git a/arbitrum/backend.go b/arbitrum/backend.go new file mode 100644 index 000000000000..a30145e91517 --- /dev/null +++ b/arbitrum/backend.go @@ -0,0 +1,104 @@ +package arbitrum + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/node" +) + +type Backend struct { + arbos ArbosWrapper + blockChain *core.BlockChain + stack *node.Node + chainId *big.Int + apiBackend *APIBackend + ethConfig *ethconfig.Config + ethDatabase ethdb.Database + + txFeed event.Feed + scope event.SubscriptionScope + + chanTxs chan *types.Transaction + chanClose chan struct{} //close coroutine + chanNewBlock chan struct{} //create new L2 block unless empty +} + +func NewBackend(stack *node.Node, config *ethconfig.Config, ethDatabase ethdb.Database, blockChain *core.BlockChain, chainId *big.Int, arbos ArbosWrapper) (*Backend, error) { + backend := &Backend{ + arbos: arbos, + blockChain: blockChain, + stack: stack, + chainId: chainId, + ethConfig: config, + ethDatabase: ethDatabase, + chanTxs: make(chan *types.Transaction, 100), + chanClose: make(chan struct{}, 1), + chanNewBlock: make(chan struct{}, 1), + } + stack.RegisterLifecycle(backend) + go backend.segmentQueueRoutine() + + createRegisterAPIBackend(backend) + return backend, nil +} + +func (b *Backend) APIBackend() *APIBackend { + return b.apiBackend +} + +func (b *Backend) EnqueueL2Message(tx *types.Transaction) error { + b.chanTxs <- tx + return nil +} + +func (b *Backend) CloseBlock() { + b.chanNewBlock <- struct{}{} +} + +func (b *Backend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return b.scope.Track(b.txFeed.Subscribe(ch)) +} + +func (b *Backend) enqueueBlock(block *types.Block, reciepts types.Receipts, state *state.StateDB) { + if block == nil { + return + } + logs := make([]*types.Log, 0) + for _, receipt := range reciepts { + logs = append(logs, receipt.Logs...) + } + b.blockChain.WriteBlockWithState(block, reciepts, logs, state, true) +} + +func (b *Backend) segmentQueueRoutine() { + for { + select { + case tx := <-b.chanTxs: + b.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}}) + b.arbos.EnqueueSequencerTx(tx) + case <-b.chanNewBlock: + b.enqueueBlock(b.arbos.BuildBlock(true)) + case <-b.chanClose: + return + } + } +} + +//TODO: this is used when registering backend as lifecycle in stack +func (b *Backend) Start() error { + return nil +} + +func (b *Backend) Stop() error { + + b.scope.Close() + b.blockChain.Stop() + + return nil +}