Skip to content

Commit

Permalink
Merge pull request #31 from lightlink-network/release/v1.0.0-alpha
Browse files Browse the repository at this point in the history
Release/v1.0.0 alpha
  • Loading branch information
Sledro authored Apr 10, 2024
2 parents fbe58bc + dd8a331 commit 369c634
Show file tree
Hide file tree
Showing 52 changed files with 6,446 additions and 1,651 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ config*
store
storage
build

.test
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,31 @@ Documentation & deployed contract addresses can be found [here](https://docs.lig

## Usage

```bash rollup info # Get the current rollup state
```bash
hb rollup info # Get the current rollup state
hb rollup info --num <rblock_number> --bundle # View the bundled L2 block hashes in an L1 rblock
hb rollup next # [Publisher Only] Generate the next rollup block
hb rollup start # [Publisher Only] Start the rollup loop to generate and submit bundles
hb challenger challenge-da <rblock_number> # Challenge data availability
hb defender defend-da <rblock_hash> # Defend data availability
hb defender info-da <rblock_hash> # Provides info on an existing challenge
hb defender prove-da <rblock_hash> # Prove data availability
hb challenger challenge-da <rblock_number> <bundle_number> # Challenge data availability
hb defender defend-da <rblock_hash> <bundle_number> # Defend data availability
hb defender info-da <rblock_hash> <bundle_number> # Provides info on an existing challenge
hb defender prove-da <rblock_hash> <bundle_number> # Prove data availability
hb defender start # Start the defender loop to watch and defend challenges
hb defender provide --type=header <rblock_hash> <l2_block_hash> # Provide will download header for the given <l2_block_hash> from Celestia and provide it to Layer 1 ChainOracle
hb defender provide --type=tx <rblock_hash> <l2_tx_hash> # Provide will download tx for the given <l2_tx_hash> from Celestia and provide it to Layer 1 ChainOracle
hb defender provide --type=header <rblock_hash> <l2_block_hash> # Get header for <l2_block_hash> from Celestia and provide it to L1 ChainOracle
hb defender provide --type=tx <rblock_hash> <l2_tx_hash> # Get tx for <l2_tx_hash> from Celestia and provide it to L1 ChainOracle
```

## Dev Commands

```bash
hbdev fetch header <rblock_hash> <l2block_hash> # Fetch and decode an L2 block header from Celesta
hbdev fetch header <rblock_hash> <l2block_hash> --proof # Fetch and return celestia DA proof for an L2 block header
hbdev fetch header <rblock_hash> <l2block_hash> --proof --check-proof # Verify the proof returned by Celestia
hbdev fetch tx <tx_hash> # Fetch and decode an L2 transaction from Celestia
hbdev fetch tx <tx_hash> --proof # Fetch and return celestia DA proof for an L2 transaction
hbdev fetch tx <tx_hash> --proof --check-proof # Verify the proof returned by Celestia
hbdev inspect <rblock_hash> --header --bundle --stats --shares --txns # Inspect will inspect a rollup block
hbdev pointer <rblock_hash> --format=pretty --verify # Pointer finds the Celestia data pointer for a given hash
```

The following root flags are available for all commands:
Expand All @@ -52,7 +66,7 @@ cd $HOME
rm -rf hummingbird-client
git clone https://github.com/lightlink-network/hummingbird-client.git
cd hummingbird-client
git checkout tags/v0.1.1 -b v0.1.1
git checkout tags/v1.0.0-alpha -b v1.0.0-alpha
make build
```

Expand Down
4 changes: 2 additions & 2 deletions challenger/challenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ func NewChallenger(node *node.Node, opts *Opts) *Challenger {
return &Challenger{Node: node, Opts: opts}
}

func (c *Challenger) ChallengeDA(index uint64) (*types.Transaction, common.Hash, error) {
return c.Ethereum.ChallengeDataRootInclusion(index)
func (c *Challenger) ChallengeDA(index uint64, pointerIndex uint8) (*types.Transaction, common.Hash, error) {
return c.Ethereum.ChallengeDataRootInclusion(index, pointerIndex)
}
69 changes: 69 additions & 0 deletions cli/dev/cmd/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package cmd

import (
"crypto/ecdsa"
"encoding/json"
"fmt"
"hummingbird/cli/hb/cmd"
"hummingbird/config"
"hummingbird/node"
"hummingbird/utils"
"log/slog"
"os"
"strings"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)

func makeNode() (*node.Node, *slog.Logger, error) {
cfg := config.Load()
log := cmd.ConsoleLogger()
ethKey := getEthKey()

n, err := node.NewFromConfig(cfg, log, ethKey)
if err != nil {
return nil, nil, err
}

return n, log, nil
}

func getEthKey() *ecdsa.PrivateKey {
key := os.Getenv("ETH_KEY")
if key == "" {
return nil
}

ethKey, err := crypto.ToECDSA(hexutil.MustDecode(key))
if err != nil {
return nil
}

return ethKey
}

// panicErr panics if err is not nil, with an optional prefix
func panicErr(err error, prefix ...string) {
if err != nil {
if len(prefix) > 0 {
panic(fmt.Errorf("%s: %w", prefix[0], err))
}
panic(err)
}
}

func printJSON(v interface{}) {
output, err := utils.PrepareTidyJSON(v)
panicErr(err, "json output preparation failed")

buf, err := json.MarshalIndent(&output, "", " ")
panicErr(err, "json output formatting failed")

fmt.Println(string(buf))
}

func printPretty(v interface{}) {
out := utils.MarshalText(v)
fmt.Println(strings.TrimSpace(string(out)))
}
215 changes: 215 additions & 0 deletions cli/dev/cmd/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package cmd

import (
"fmt"
"math/big"

"github.com/celestiaorg/celestia-app/pkg/shares"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/spf13/cobra"

"hummingbird/node"
"hummingbird/node/contracts"
chainoracleContract "hummingbird/node/contracts/ChainOracle.sol"
"hummingbird/rollup"
"hummingbird/utils"
)

func init() {
FetchCmd.Flags().StringVar(&format, "format", "json", "format of the output (json, pretty)")
FetchCmd.Flags().BoolVar(&withProof, "proof", false, "whether to generate share proofs in the output")
FetchCmd.Flags().BoolVar(&checkProof, "check-proof", false, "whether to check the proof")
FetchCmd.Flags().BoolVar(&checkPointer, "check-pointer", false, "whether to check the pointer")

// Add the fetch command to the root command
RootCmd.AddCommand(FetchCmd)
}

var (
// variables for the fetch command
format string // format of the output (json)
withProof bool // whether to generate share proofs in the output
checkProof bool // whether to check the proof
checkPointer bool // whether to check the pointer

// fetch command
FetchCmd = &cobra.Command{
Use: "fetch",
Short: "fetch will fetch an item (either: header or tx) from a given rollup block",
Long: "fetch will fetch an item (either: header or tx) from a given rollup block. This can be used for generating test data for the smart contracts.",
Args: cobra.MinimumNArgs(3),
ArgAliases: []string{
"data-type",
"rblock",
"data-hash",
},
Run: func(cmd *cobra.Command, args []string) {

// 0. parse args
dataType := args[0]
rblockHash := common.HexToHash(args[1])
dataHash := common.HexToHash(args[2])

// 1. make node
n, log, err := makeNode()
panicErr(err, "failed to create node")
r := rollup.NewRollup(n, &rollup.Opts{
Logger: log.With("ctx", "Rollup"),
})

// 2. Get rblock and celestia pointer
rblock, err := r.GetBlockByHash(rblockHash)
panicErr(err, "failed to get rollup block")
log.Debug("✔️ Got Rollup Block", "hash", rblockHash)

// 3. Fetch the item
var item any
var sharePointer *node.SharePointer
var celPointer *node.CelestiaPointer
var pointerIndex uint8

switch dataType {
case "header":
sharePointer, pointerIndex, err = node.FindHeaderSharesInBundles(rblock.Bundles, dataHash, n.Namespace())
celPointer = rblock.GetCelestiaPointers()[int(pointerIndex)]
panicErr(err, "failed to find header shares")
h := &types.Header{}
err = rlp.DecodeBytes(sharePointer.Bytes(), h)
panicErr(err, "failed to decode header")
item = &h
case "tx":
sharePointer, pointerIndex, err = node.FindTxSharesInBundles(rblock.Bundles, dataHash, n.Namespace())
celPointer = rblock.GetCelestiaPointers()[int(pointerIndex)]
panicErr(err, "failed to find tx shares")
tx := &types.Transaction{}
err = rlp.DecodeBytes(sharePointer.Bytes(), tx)
panicErr(err, "failed to decode tx")
item = &tx
default:
panicErr(err, "invalid data type")
}
log.Debug("✔️ Fetched Item", "type", dataType)

if checkPointer {
log.Warn("Not implemented: check-pointer")
// ss, err := n.Celestia.GetShares(&node.CelestiaPointer{
// Height: celPointer.Height,
// ShareStart: uint64(pointer.StartShare),
// ShareLen: uint64(pointer.EndShare()-pointer.StartShare) + 1,
// })
// panicErr(err, "failed to get shares")

// for i, s := range pointer.Shares() {
// if !bytes.Equal(s.ToBytes(), ss[i].ToBytes()) {
// log.Info("share pointer length", "expect", uint64(pointer.EndShare()-pointer.StartShare), "got", len(pointer.Ranges))
// log.Error("share does not match", "index", i, "expected", fmt.Sprintf("%x", s), "got", fmt.Sprintf("%x", ss[i]))
// panic("check-pointer: share does not match")
// }
// log.Debug("✔️ Share matches", "index", i)
// }
}

// 5. Generate proofs
var proof *chainoracleContract.SharesProof
if withProof {
shareProof, err := n.Celestia.GetSharesProof(celPointer, sharePointer)
panicErr(err, "failed to get share proof")

commitment, err := n.Ethereum.GetBlobstreamCommitment(int64(celPointer.Height))
panicErr(err, "failed to get blobstream commitment")

celProof, err := n.Celestia.GetProof(celPointer, commitment.StartBlock, commitment.EndBlock, *commitment.ProofNonce)
panicErr(err, "failed to get celestia proof")

attestationProof := chainoracleContract.AttestationProof{
TupleRootNonce: celProof.Nonce,
Tuple: chainoracleContract.DataRootTuple{
Height: celProof.Tuple.Height,
DataRoot: celProof.Tuple.DataRoot,
},
Proof: chainoracleContract.BinaryMerkleProof{
SideNodes: celProof.WrappedProof.SideNodes,
Key: celProof.WrappedProof.Key,
NumLeaves: celProof.WrappedProof.NumLeaves,
},
}

proof, err = contracts.NewShareProof(shareProof, attestationProof)
panicErr(err, "failed to create proof")

// check the proof can be decoded
if checkProof {
ss, err := utils.BytesToShares(proof.Data)
panicErr(err, "failed to convert proof to shares")
switch dataType {
case "header":
decH, err := sharesToHeader(ss, sharePointer.Ranges)
panicErr(err, "failed to convert shares to header")

decH.Extra = common.Hex2Bytes("0x")
if decH.Hash().Hex() != dataHash.Hex() {
panicErr(err, "proof does not match data")
}

default:
log.Warn("proof check not implemented for tx")
}

fmt.Println("✔️ Proof is valid")
}
}

// 5. Generate output
ranges := []chainoracleContract.ChainOracleShareRange{}
for _, r := range sharePointer.Ranges {
ranges = append(ranges, chainoracleContract.ChainOracleShareRange{
Start: big.NewInt(int64(r.Start)),
End: big.NewInt(int64(r.End)),
})
}

shareBytes := [][]byte{}
for _, s := range sharePointer.Shares() {
shareBytes = append(shareBytes, s.ToBytes())
}

output := &Output[any]{
RBlock: rblockHash,
Hash: dataHash,
Data: item,
Shares: shareBytes,
Proof: proof,
Ranges: ranges,
}

// 6. Print output
switch format {
case "json":
printJSON(output)
default:
printPretty(output)
}
},
}
)

type Output[T any] struct {
RBlock common.Hash `json:"rblock"`
Hash common.Hash `json:"hash"`
Data T `json:"content"`
Shares [][]byte `json:"shares,omitempty"`
Ranges []chainoracleContract.ChainOracleShareRange `json:"ranges,omitempty"`
Proof *chainoracleContract.SharesProof `json:"proof,omitempty"`
}

func sharesToHeader(s []shares.Share, ranges []node.ShareRange) (*types.Header, error) {
data := []byte{}
for i, r := range ranges {
data = append(data, s[i].ToBytes()[r.Start:r.End]...)
}

header := &types.Header{}
return header, rlp.DecodeBytes(data, &header)
}
Loading

0 comments on commit 369c634

Please sign in to comment.