Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature l2to l1 #75

Merged
merged 6 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions cairo/adapters/messages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package adapters

import (
"errors"
"fmt"
"github.com/NethermindEth/juno/core/felt"
"github.com/ethereum/go-ethereum/common"

"math/big"
)

type MessageL1ToL2 struct {
// The address of the L1 contract sending the message.
From common.Address `json:"from_address" validate:"required"`
// The address of the L1 contract sending the message.
To felt.Felt `json:"to_address" validate:"required"`
Nonce felt.Felt `json:"nonce" validate:"required"`
Selector felt.Felt `json:"entry_point_selector" validate:"required"`
// The payload of the message.
Payload []felt.Felt `json:"payload" validate:"required"`
}

func (m *MessageL1ToL2) EncodeTo() ([]*big.Int, error) {
var result []*big.Int

// From
result = append(result, new(big.Int).SetBytes(m.From.Bytes()))

// To
if m.To.IsZero() {
return nil, errors.New("To field is zero (invalid)")
}
result = append(result, m.To.BigInt(new(big.Int)))

// Nonce
result = append(result, m.Nonce.BigInt(new(big.Int)))

// Selector
if m.Selector.IsZero() {
return nil, errors.New("Selector field is zero (invalid)")
}
result = append(result, m.Selector.BigInt(new(big.Int)))

payloadSize := big.NewInt(int64(len(m.Payload)))
result = append(result, payloadSize)
// Payload
for _, p := range m.Payload {
if !p.IsZero() {
result = append(result, p.BigInt(new(big.Int)))
}
}

return result, nil
}

func (m *MessageL1ToL2) SizeInFelts() int {
size := 0
size += sizeOfCommonAddress(m.From)
size += sizeOfFelt(m.To)
size += sizeOfFelt(m.Selector)
// for payload length field
size += 1
for _, p := range m.Payload {
size += sizeOfFelt(p)
}
return size
}

func sizeOfCommonAddress(addr common.Address) int {
return 1
}

func sizeOfFelt(f felt.Felt) int {
return 1
}

// MessageL2ToL1 L2ToL1Message
type MessageL2ToL1 struct {
From *felt.Felt `json:"from_address,omitempty"`
To common.Address `json:"to_address"`

Payload []*felt.Felt `json:"payload"`
}

func (m *MessageL2ToL1) EncodeTo() ([]*big.Int, error) {
var result []*big.Int

// From
if m.From != nil {
result = append(result, m.From.BigInt(new(big.Int)))
} else {
return nil, errors.New("From field is nil")
}

// To
result = append(result, new(big.Int).SetBytes(m.To.Bytes()))
fmt.Println("To:", new(big.Int).SetBytes(m.To.Bytes()))

payloadSize := big.NewInt(int64(len(m.Payload)))
result = append(result, payloadSize)
// Payload
for _, p := range m.Payload {
if p != nil {
result = append(result, p.BigInt(new(big.Int)))
}
}

return result, nil
}

func (m *MessageL2ToL1) SizeInFelts() int {
size := 0

if m.From != nil {
size += sizeOfFelt(*m.From)
}

size += sizeOfCommonAddress(m.To)

// for payload length field
size += 1

for _, p := range m.Payload {
size += sizeOfFelt(*p)
}

return size
}
151 changes: 148 additions & 3 deletions cairo/cairo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package cairo

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

"fmt"
junostate "github.com/NethermindEth/juno/blockchain"
"github.com/NethermindEth/juno/core"
"github.com/NethermindEth/juno/core/felt"
Expand All @@ -15,10 +13,21 @@ import (
"github.com/NethermindEth/juno/utils"
"github.com/NethermindEth/juno/vm"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/sirupsen/logrus"
"github.com/yu-org/yu/core/context"
"github.com/yu-org/yu/core/tripod"
"github.com/yu-org/yu/core/types"
. "github.com/yu-org/yu/core/types"
"github.com/yu-org/yu/utils/log"
"itachi/cairo/adapters"
"itachi/cairo/config"
"itachi/cairo/l1/contract"
snos_ouput "itachi/cairo/snos-ouput"
"math/big"
"net/http"
"os"
)

type Cairo struct {
Expand All @@ -30,6 +39,142 @@ type Cairo struct {
network utils.Network
}

func (c *Cairo) FinalizeBlock(block *Block) {
logrus.SetOutput(os.Stdout)

if !c.cfg.EnableL2UpdateState {

return
}

// for PrevStateRoot, get last finalized block
compactBlock, err := c.Chain.LastFinalized()
if err != nil {
logrus.Fatal("get compactBlock for finalize block failed: ", err)
}

var starkReceipt *rpc.TransactionReceipt
txns := block.Txns.ToArray()
messagesToL1 := make([]*rpc.MsgToL1, 0)
for t := 0; t < len(txns); t++ {
txn := txns[t]
receipt, _ := c.TxDB.GetReceipt(txn.TxnHash)
receiptExtraByt := receipt.Extra
err := encoder.Unmarshal(receiptExtraByt, &starkReceipt)
if err != nil {
// handle error
logrus.Fatal("unmarshal starkReceipt failed: ", err)
} else {
messagesToL1 = append(messagesToL1, starkReceipt.MessagesSent...)
}
}
// Adapt
messageL2ToL1 := make([]*adapters.MessageL2ToL1, len(messagesToL1))
for idx, msg := range messagesToL1 {
messageL2ToL1[idx] = &adapters.MessageL2ToL1{
From: msg.From,
To: msg.To,
Payload: msg.Payload,
}
}

// todo messagesToL2 := make([]*rpc.MsgFromL1, 0)
messagesToL2 := make([]*adapters.MessageL1ToL2, 0)
//for t := 0; t < len(txns); t++ {
// txn := txns[t]
//
//}

num := uint64(block.Height)
// init StarknetOsOutput by block
snOsOutput := &snos_ouput.StarknetOsOutput{
PrevStateRoot: new(felt.Felt).SetBytes(compactBlock.StateRoot.Bytes()),
NewStateRoot: new(felt.Felt).SetBytes(block.StateRoot.Bytes()),
BlockNumber: new(felt.Felt).SetUint64(num),
BlockHash: new(felt.Felt).SetBytes(block.Hash.Bytes()),
ConfigHash: new(felt.Felt).SetUint64(0),
KzgDA: new(felt.Felt).SetUint64(0),
MessagesToL1: messageL2ToL1,
MessagesToL2: messagesToL2,
}
// cairoState.UpdateStarknetOsOutput(snOsOutput)
fmt.Printf("snOsOutput:\n%+v\n", snOsOutput)

// 新旧状态根对比
if snOsOutput.PrevStateRoot.String() != snOsOutput.NewStateRoot.String() {
// send snOsOutput to L1 chain
c.ethCallUpdateState(c.cairoState, snOsOutput)
}

log.DoubleLineConsole.Info(fmt.Sprintf("Cairo Tripod finalize block, height=%d, hash=%s", block.Height, block.Hash.String()))

}

func (c *Cairo) ethCallUpdateState(cairoState *CairoState, snOsOutput *snos_ouput.StarknetOsOutput) {

client, err := ethclient.Dial(c.cfg.EthRpcUrl)
if err != nil {
fmt.Println("init client failed: ", err)
}

starknetCore, err := contract.NewStarknetCore(common.HexToAddress(c.cfg.EthCoreContractAddress), client)
if err != nil {
fmt.Println("init starknetCore failed: ", err)
return
}

// encode snOsOutput to []*big.Int
programOutput, err := snOsOutput.EncodeTo()
if err != nil {
fmt.Println("encode snOsOutput failed: ", err)
return
}

// compute onchainDataHash and onchainDataSize
onchainDataHash, onchainDataSize, err := calculateOnchainData(programOutput)
if err != nil {
fmt.Println("calculate onchain data failed: ", err)
return
}

chainID := big.NewInt(c.cfg.ChainID)
privateKeyHex := c.cfg.EthPrivateKey
address := c.cfg.EthWalletAddress
gasLimit := c.cfg.GasLimit
auth, err := CreateAuth(client, privateKeyHex, address, gasLimit, chainID)
if err != nil {
fmt.Println("create auth failed: ", err)
return
}

// call updateState
tx, err := starknetCore.UpdateState(auth, programOutput, onchainDataHash, onchainDataSize)
if err != nil {
fmt.Println("call updateState failed: %s", err)
return
}

// retrieve transaction hash and print.
txHash := tx.Hash()
log.DoubleLineConsole.Info("update state, tx hash: %s", txHash.Hex())
fmt.Println("https://sepolia.etherscan.io/tx/" + tx.Hash().Hex())

}

func calculateOnchainData(programOutput []*big.Int) (*big.Int, *big.Int, error) {
var data []byte
for _, output := range programOutput {
data = append(data, output.Bytes()...)
}
onchainDataHash := crypto.Keccak256Hash(data)
onchainDataHashBig := new(big.Int).SetBytes(onchainDataHash.Bytes())

// compute onchainDataSize
onchainDataSize := new(big.Int).SetInt64(int64(len(programOutput)))

return onchainDataHashBig, onchainDataSize, nil
}

func NewCairo(cfg *config.Config) *Cairo {
state, err := NewCairoState(cfg)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions cairo/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ type Config struct {
EthClientAddress string `toml:"eth_client_address"`
EthContractAddress string `toml:"eth_contract_address"`

// L2 eth client configs
EnableL2UpdateState bool `toml:"enable_l2_update_state"`
EthRpcUrl string `toml:"eth_rpc_url"`
ChainID int64 `toml:"chain_id"`
GasLimit uint64 `toml:"gas_limit"`
EthCoreContractAddress string `toml:"eth_core_contract_address"`
EthWalletAddress string `toml:"eth_wallet_address"`
EthPrivateKey string `toml:"eth_private_key"`

EnablePprof bool `toml:"enable_pprof"`
PprofAddr string `toml:"pprof_addr"`
}
Expand Down
56 changes: 56 additions & 0 deletions cairo/eth_send.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cairo

import (
"context"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"math/big"
)

func CreateAuth(client *ethclient.Client, privateKeyHex string, address string, gasLimit uint64, chainID *big.Int) (*bind.TransactOpts, error) {
// Get nonce
nonce, err := client.PendingNonceAt(context.Background(), common.HexToAddress(address))
if err != nil {
return nil, err
}

// Get gas price
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
return nil, err
}

// Ensure private key string has 0x prefix
if !has0xPrefix(privateKeyHex) {
privateKeyHex = "0x" + privateKeyHex
}

// Decode private key
rawPrivateKey, err := hexutil.Decode(privateKeyHex)
if err != nil {
return nil, err
}

// Convert to ECDSA private key
privateKey, err := crypto.ToECDSA(rawPrivateKey)
if err != nil {
return nil, err
}

// Create auth object
auth, _ := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // 0 value for no ether transfer
auth.GasLimit = gasLimit
auth.GasPrice = gasPrice

return auth, nil
}

// Helper function to check if string has 0x prefix
func has0xPrefix(s string) bool {
return len(s) >= 2 && s[0:2] == "0x"
}
Loading
Loading