Skip to content

Commit

Permalink
L1 -> L2 (#62)
Browse files Browse the repository at this point in the history
* add l1 contract

* rename pkg name

* parse event l1Txn

* add some

* upgrade yu

* Revert "upgrade yu"

This reverts commit adf05dd.

* handle StarknetLogMessageToL2 (#67)

* handle StarknetLogMessageToL2

* chore: done some changes

* reuse client

* Use fatal to handle NewCairo error

* Pass starknetrpc.StarknetRPC on NewL1

* Handle fee in convertL1TxnToBroadcastedTxn

* some fix

* some fix

* rm sleep

---------

Co-authored-by: Lawliet-Chan <[email protected]>

* enable l1

* fix

---------

Co-authored-by: Nordsche Sasa <[email protected]>
  • Loading branch information
Lawliet-Chan and nordschesasa authored May 31, 2024
1 parent d227b0d commit 4384f35
Show file tree
Hide file tree
Showing 13 changed files with 1,456 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
build:
name: Build Test
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
5 changes: 3 additions & 2 deletions cairo/cairo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package cairo

import (
"encoding/hex"
"itachi/cairo/config"
"net/http"

junostate "github.com/NethermindEth/juno/blockchain"
"github.com/NethermindEth/juno/core"
"github.com/NethermindEth/juno/core/felt"
Expand All @@ -16,8 +19,6 @@ import (
"github.com/yu-org/yu/core/context"
"github.com/yu-org/yu/core/tripod"
"github.com/yu-org/yu/core/types"
"itachi/cairo/config"
"net/http"
)

type Cairo struct {
Expand Down
6 changes: 6 additions & 0 deletions cairo/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@ type Config struct {
GenesisContracts map[string]string `toml:"genesis_contracts"`
GenesisStorages []*GenesisStorage `toml:"genesis_storages"`

// RPC configs
EnableStarknetRPC bool `toml:"enable_starknet_rpc"`
StarknetHost string `toml:"starknet_host"`
StarknetPort string `toml:"starknet_port"`

// L1 configs
EnableL1 bool `toml:"enable_l1"`
EthClientAddress string `toml:"eth_client_address"`
EthContractAddress string `toml:"eth_contract_address"`

EnablePprof bool `toml:"enable_pprof"`
PprofAddr string `toml:"pprof_addr"`
}
Expand Down
1,146 changes: 1,146 additions & 0 deletions cairo/l1/contract/l1.go

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions cairo/l1/contract/solidity/IStarknetMessagingEvents.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2019-2024 StarkWare Industries Ltd.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.starkware.co/open-source-license/
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions
and limitations under the License.
*/
// SPDX-License-Identifier: Apache-2.0.
pragma solidity ^0.8.0;

interface IStarknetMessagingEvents {
// This event needs to be compatible with the one defined in Output.sol.
event LogMessageToL1(uint256 indexed fromAddress, address indexed toAddress, uint256[] payload);

// An event that is raised when a message is sent from L1 to L2.
event LogMessageToL2(
address indexed fromAddress,
uint256 indexed toAddress,
uint256 indexed selector,
uint256[] payload,
uint256 nonce,
uint256 fee
);

// An event that is raised when a message from L2 to L1 is consumed.
event ConsumedMessageToL1(
uint256 indexed fromAddress,
address indexed toAddress,
uint256[] payload
);

// An event that is raised when a message from L1 to L2 is consumed.
event ConsumedMessageToL2(
address indexed fromAddress,
uint256 indexed toAddress,
uint256 indexed selector,
uint256[] payload,
uint256 nonce
);

// An event that is raised when a message from L1 to L2 Cancellation is started.
event MessageToL2CancellationStarted(
address indexed fromAddress,
uint256 indexed toAddress,
uint256 indexed selector,
uint256[] payload,
uint256 nonce
);

// An event that is raised when a message from L1 to L2 is canceled.
event MessageToL2Canceled(
address indexed fromAddress,
uint256 indexed toAddress,
uint256 indexed selector,
uint256[] payload,
uint256 nonce
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromAddress","type":"uint256"},{"indexed":true,"internalType":"address","name":"toAddress","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"payload","type":"uint256[]"}],"name":"ConsumedMessageToL1","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"toAddress","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"selector","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"payload","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"ConsumedMessageToL2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromAddress","type":"uint256"},{"indexed":true,"internalType":"address","name":"toAddress","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"payload","type":"uint256[]"}],"name":"LogMessageToL1","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"toAddress","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"selector","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"payload","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"LogMessageToL2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"toAddress","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"selector","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"payload","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"MessageToL2Canceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"toAddress","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"selector","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"payload","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"MessageToL2CancellationStarted","type":"event"}]
87 changes: 87 additions & 0 deletions cairo/l1/eth_sub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package l1

import (
"context"
"fmt"
"itachi/cairo/l1/contract"
"math/big"
"strconv"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
)

type EthSubscriber struct {
ethClient *ethclient.Client
client *rpc.Client
filterer *contract.StarknetFilterer
}

func NewEthSubscriber(ethClientAddress string, coreContractAddress common.Address) (*EthSubscriber, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// TODO replace with our own client once we have one.
// Geth pulls in a lot of dependencies that we don't use.
client, err := rpc.DialContext(ctx, ethClientAddress)
if err != nil {
return nil, err
}
ethClient := ethclient.NewClient(client)
filterer, err := contract.NewStarknetFilterer(coreContractAddress, ethClient)
if err != nil {
return nil, err
}
return &EthSubscriber{
ethClient: ethClient,
client: client,
filterer: filterer,
}, nil
}

func (s *EthSubscriber) WatchLogMessageToL2(
ctx context.Context,
sink chan<- *contract.StarknetLogMessageToL2,
fromAddress []common.Address,
toAddress []*big.Int,
selector []*big.Int,
) (event.Subscription, error) {
return s.filterer.WatchLogMessageToL2(&bind.WatchOpts{Context: ctx}, sink, fromAddress, toAddress, selector)
}

func (s *EthSubscriber) ChainID(ctx context.Context) (*big.Int, error) {
return s.ethClient.ChainID(ctx)
}

func (s *EthSubscriber) FinalisedHeight(ctx context.Context) (uint64, error) {
finalisedBlock := make(map[string]any, 0)
if err := s.client.CallContext(ctx, &finalisedBlock, "eth_getBlockByNumber", "finalized", false); err != nil { //nolint:misspell
return 0, fmt.Errorf("get finalised Ethereum block: %w", err)
}

number, ok := finalisedBlock["number"] //nolint:gosec
if !ok {
return 0, fmt.Errorf("number field not present in Ethereum block")
}

numberString, ok := number.(string)
if !ok {
return 0, fmt.Errorf("block number is not a string: %v", number)
}

numberString = strings.TrimPrefix(numberString, "0x")
numberUint, err := strconv.ParseUint(numberString, 16, 64)
if err != nil {
return 0, fmt.Errorf("parse block number: %s", numberString)
}

return numberUint, nil
}

func (s *EthSubscriber) Close() {
s.ethClient.Close()
}
112 changes: 112 additions & 0 deletions cairo/l1/l1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package l1

import (
"context"
"fmt"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/juno/rpc"
"github.com/ethereum/go-ethereum/common"
"github.com/sirupsen/logrus"
"github.com/yu-org/yu/core/kernel"
"itachi/cairo/config"
"itachi/cairo/l1/contract"
"itachi/cairo/starknetrpc"
"math/big"
)

type L1 struct {
itachi *kernel.Kernel
ethL1 *EthSubscriber
starknetRPC *starknetrpc.StarknetRPC
}

func NewL1(itachi *kernel.Kernel, cfg *config.Config, s *starknetrpc.StarknetRPC) (*L1, error) {
ethL1, err := NewEthSubscriber(cfg.EthClientAddress, common.HexToAddress(cfg.EthContractAddress))
if err != nil {
return nil, err
}
return &L1{
itachi: itachi,
ethL1: ethL1,
starknetRPC: s,
}, nil
}

func StartupL1(itachi *kernel.Kernel, cfg *config.Config, s *starknetrpc.StarknetRPC) {
if cfg.EnableL1 {
l1, err := NewL1(itachi, cfg, s)
if err != nil {
logrus.Fatal("init L1 client failed: ", err)
}
err = l1.Run(context.Background())
if err != nil {
logrus.Fatal("l1 client run failed: ", err)
}
}
}

func (l *L1) Run(ctx context.Context) error {
msgChan := make(chan *contract.StarknetLogMessageToL2)
sub, err := l.ethL1.WatchLogMessageToL2(ctx, msgChan, nil, nil, nil)
if err != nil {
return err
}

// Listen for msgChan
go func() {
for {
select {
case msg := <-msgChan:
broadcastedTxn, err := convertL1TxnToBroadcastedTxn(msg)
if err != nil {
logrus.Errorf("Error converting L1 txn to broadcasted txn: %v", err)
continue
}
response, jsonRpcErr := l.starknetRPC.AddTransaction(*broadcastedTxn)
if jsonRpcErr != nil {
logrus.Errorf("Error adding transaction: %v", err)
} else {
logrus.Infof("L1 Transaction added: %v", response)
}
case subErr := <-sub.Err():
logrus.Errorf("L1 update subscription failed: %v, Resubscribing...", subErr)
sub.Unsubscribe()

sub, err = l.ethL1.WatchLogMessageToL2(ctx, msgChan, nil, nil, nil)
if err != nil {
logrus.Errorf("Resubscribe failed: %v", err)
}
case <-ctx.Done():
sub.Unsubscribe()
return
}
}
}()
return nil
}

func convertL1TxnToBroadcastedTxn(event *contract.StarknetLogMessageToL2) (*rpc.BroadcastedTransaction, error) {
callData := make([]*felt.Felt, 0)
callData = append(callData, new(felt.Felt).SetBigInt(event.FromAddress.Big()))
for _, payload := range event.Payload {
data := new(felt.Felt).SetBigInt(payload)
callData = append(callData, data)
}

maxU128 := new(big.Int).SetUint64(1<<64 - 1)

// Check if fee exceeds u128 max value
if event.Fee.Cmp(maxU128) > 0 {
return nil, fmt.Errorf("fee exceeds u128 max value")
}

return &rpc.BroadcastedTransaction{
Transaction: rpc.Transaction{
Type: rpc.TxnL1Handler,
ContractAddress: new(felt.Felt).SetBigInt(event.ToAddress),
Nonce: new(felt.Felt).SetBigInt(event.Nonce),
CallData: &callData,
},
PaidFeeOnL1: new(felt.Felt).SetBigInt(event.Fee),
}, nil
}
7 changes: 5 additions & 2 deletions cairo/starknetrpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package starknetrpc
import (
"context"
"errors"
"github.com/sirupsen/logrus"
"itachi/cairo/config"
"net"
"net/http"
"runtime"
"time"

"github.com/sirupsen/logrus"

"github.com/NethermindEth/juno/jsonrpc"
"github.com/NethermindEth/juno/utils"
"github.com/NethermindEth/juno/validator"
Expand Down Expand Up @@ -92,7 +93,7 @@ func (s *StarknetRPC) Serve(ctx context.Context) error {
}
}

func StartUpStarknetRPC(chain *kernel.Kernel, cfg *config.Config) {
func StartUpStarknetRPC(chain *kernel.Kernel, cfg *config.Config) *StarknetRPC {
if cfg.EnableStarknetRPC {
rpcSrv, err := NewStarknetRPC(chain, cfg)
if err != nil {
Expand All @@ -106,7 +107,9 @@ func StartUpStarknetRPC(chain *kernel.Kernel, cfg *config.Config) {
logrus.Errorf("starknetRPC serves failed, %v", err)
}
}()
return rpcSrv
}
return nil
}

func exactPathServer(path string, handler http.Handler) http.HandlerFunc {
Expand Down
19 changes: 14 additions & 5 deletions cmd/node/app/app.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
package app

import (
"github.com/common-nighthawk/go-figure"
"github.com/yu-org/yu/apps/poa"
"github.com/yu-org/yu/core/kernel"
"github.com/yu-org/yu/core/startup"
"itachi/cairo"
"itachi/cairo/config"
"itachi/cairo/l1"
"itachi/cairo/starknetrpc"
"itachi/utils"

"github.com/common-nighthawk/go-figure"
"github.com/yu-org/yu/apps/poa"
"github.com/yu-org/yu/core/kernel"
"github.com/yu-org/yu/core/startup"
)

func StartUpChain(poaCfg *poa.PoaConfig, crCfg *config.Config) {
figure.NewColorFigure("Itachi", "big", "green", false).Print()

chain := InitItachi(poaCfg, crCfg)
starknetrpc.StartUpStarknetRPC(chain, crCfg)

// Starknet RPC server
rpcSrv := starknetrpc.StartUpStarknetRPC(chain, crCfg)

// Subscribe to L1
l1.StartupL1(chain, crCfg, rpcSrv)

utils.StartUpPprof(crCfg)
chain.Startup()

}

func InitItachi(poaCfg *poa.PoaConfig, crCfg *config.Config) *kernel.Kernel {
Expand Down
Loading

0 comments on commit 4384f35

Please sign in to comment.