Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

Commit

Permalink
feat(prover): change block signing to use timestamp as key (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhorsey committed Dec 7, 2023
1 parent e48a1b6 commit eb5bc7a
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
with:
repository: taikoxyz/taiko-mono
path: ${{ env.TAIKO_MONO_DIR }}
ref: based_contestable_zkrollup_improved
ref: based_contestable_zkrollup

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
Expand Down
45 changes: 41 additions & 4 deletions prover/db/db.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
package db

import "fmt"
import (
"bytes"
"math/big"
"strconv"

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

var (
BlockKeyPrefix = "blockid-"
BlockKeyPrefix = "block-"
)

func BuildBlockKey(blockID string) []byte {
return []byte(fmt.Sprintf("%v%v", BlockKeyPrefix, blockID))
type SignedBlockData struct {
BlockID *big.Int
BlockHash common.Hash
Signature string
}

// BuildBlockKey will build a block key for a signed block
func BuildBlockKey(blockTimestamp uint64) []byte {
return bytes.Join(
[][]byte{
[]byte(BlockKeyPrefix),
[]byte(strconv.Itoa(int(blockTimestamp))),
}, []byte{})
}

// BuildBlockValue will build a block value for a signed block
func BuildBlockValue(hash []byte, signature []byte, blockID *big.Int) []byte {
return bytes.Join(
[][]byte{
hash,
signature,
blockID.Bytes(),
}, []byte("-"))
}

func SignedBlockDataFromValue(val []byte) SignedBlockData {
v := bytes.Split(val, []byte("-"))

return SignedBlockData{
BlockID: new(big.Int).SetBytes(v[2]),
BlockHash: common.BytesToHash(v[0]),
Signature: common.Bytes2Hex(v[1]),
}
}
26 changes: 25 additions & 1 deletion prover/db/db_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
package db

import (
"bytes"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
)

func Test_BuildBlockKey(t *testing.T) {
assert.Equal(t, BuildBlockKey("1"), []byte("blockid-1"))
assert.Equal(t, []byte("block-1"), BuildBlockKey(1))
}

func Test_BuildBlockValue(t *testing.T) {
v := BuildBlockValue([]byte("hash"), []byte("sig"), big.NewInt(1))
spl := bytes.Split(v, []byte("-"))
assert.Equal(t, "hash", string(spl[0]))
assert.Equal(t, "sig", string(spl[1]))
assert.Equal(t, uint64(1), new(big.Int).SetBytes(spl[2]).Uint64())
}

func Test_SignedBlockDataFromValue(t *testing.T) {
hash := common.HexToHash("1ada5c5ba58cfca1fbcd4531f4132f8cfef736c2cf40209a1315c489717dfc49")
// nolint: lll
sig := common.Hex2Bytes("789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301")

v := BuildBlockValue(hash.Bytes(), sig, big.NewInt(1))
data := SignedBlockDataFromValue(v)

assert.Equal(t, common.Bytes2Hex(sig), data.Signature)
assert.Equal(t, hash, data.BlockHash)
assert.Equal(t, data.BlockID, big.NewInt(1))
}
34 changes: 18 additions & 16 deletions prover/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -1263,16 +1263,6 @@ func (p *Prover) signBlock(ctx context.Context, blockID *big.Int) error {

log.Info("guardian prover signing block", "blockID", blockID.Uint64())

exists, err := p.db.Has(db.BuildBlockKey(blockID.String()))
if err != nil {
return err
}

if exists {
log.Info("guardian prover already signed block", "blockID", blockID.Uint64())
return nil
}

latest, err := p.rpc.L2.BlockByNumber(ctx, nil)
if err != nil {
return err
Expand All @@ -1291,22 +1281,34 @@ func (p *Prover) signBlock(ctx context.Context, blockID *big.Int) error {
}
}

log.Info("guardian prover block signing caught up",
"latestBlock", latest.Number().Uint64(),
"eventBlockID", blockID.Uint64(),
)

block, err := p.rpc.L2.BlockByNumber(ctx, blockID)
if err != nil {
return err
}

exists, err := p.db.Has(db.BuildBlockKey(block.Time()))
if err != nil {
return err
}

if exists {
log.Info("guardian prover already signed block", "blockID", blockID.Uint64())
return nil
}

log.Info("guardian prover block signing caught up",
"latestBlock", latest.Number().Uint64(),
"eventBlockID", blockID.Uint64(),
)

signed, err := crypto.Sign(block.Hash().Bytes(), p.proverPrivateKey)
if err != nil {
return err
}

if err := p.db.Put(db.BuildBlockKey(blockID.String()), signed); err != nil {
val := db.BuildBlockValue(block.Hash().Bytes(), signed, blockID)

if err := p.db.Put(db.BuildBlockKey(block.Time()), val); err != nil {
return err
}

Expand Down
66 changes: 41 additions & 25 deletions prover/server/api.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package server

import (
"fmt"
"math/big"
"net/http"
"strconv"
"strings"
"time"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -235,45 +235,61 @@ type SignedBlock struct {
// @Success 200 {object} []SignedBlock
// @Router /signedBlocks [get]
func (srv *ProverServer) GetSignedBlocks(c echo.Context) error {
latestBlock, err := srv.rpc.L2.BlockByNumber(c.Request().Context(), nil)
if err != nil {
if err != nil {
log.Error("Failed to get latest L2 block", "error", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
var signedBlocks []SignedBlock = []SignedBlock{}

// start iterator at at provided timestamp or 0 if not defined
start := "0"
if c.QueryParam("start") != "" {
start = c.QueryParam("start")
}

var signedBlocks []SignedBlock
// if no start timestamp was provided, we can get the latest block, and return
// defaultNumBlocksToReturn blocks signed before latest, if our guardian prover has signed them.
if start == "0" {
latestBlock, err := srv.rpc.L2.BlockByNumber(c.Request().Context(), nil)
if err != nil {
if err != nil {
log.Error("Failed to get latest L2 block", "error", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
}

// start iterator at 0
start := big.NewInt(0)
// if latestBlock is greater than the number of blocks to return, we only want to return
// the most recent N blocks signed by this guardian prover.
if latestBlock.NumberU64() > defaultNumBlocksToReturn.Uint64() {
blockNum := new(big.Int).Sub(latestBlock.Number(), defaultNumBlocksToReturn)
block, err := srv.rpc.L2.BlockByNumber(
c.Request().Context(),
blockNum,
)
if err != nil {
log.Error("Failed to get L2 block", "error", err, "blockNum", blockNum)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}

// if latestBlock is greater than the number of blocks to return, we only want to return
// the most recent N blocks signed by this guardian prover.
if latestBlock.NumberU64() > numBlocksToReturn.Uint64() {
start = new(big.Int).Sub(latestBlock.Number(), numBlocksToReturn)
start = strconv.Itoa(int(block.Time()))
}
}

iter := srv.db.NewIterator([]byte(db.BlockKeyPrefix), start.Bytes())
// start should be set to a block timestamp latestBlock-numBlocksToReturn blocks ago if
// a start timestamp was not provided.

iter := srv.db.NewIterator([]byte(db.BlockKeyPrefix), []byte(start))

defer iter.Release()

for iter.Next() {
k := strings.Split(string(iter.Key()), "-")

blockID, err := strconv.Atoi(k[1])

if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
signedblockData := db.SignedBlockDataFromValue(iter.Value())

signedBlocks = append(signedBlocks, SignedBlock{
BlockID: uint64(blockID),
BlockHash: latestBlock.Hash().Hex(),
Signature: common.Bytes2Hex(iter.Value()),
BlockID: signedblockData.BlockID.Uint64(),
BlockHash: signedblockData.BlockHash.Hex(),
Signature: signedblockData.Signature,
Prover: srv.proverAddress,
})
}

fmt.Println("done")

return c.JSON(http.StatusOK, signedBlocks)
}
32 changes: 21 additions & 11 deletions prover/server/api_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package server

import (
"context"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -59,14 +60,26 @@ func (s *ProverServerTestSuite) TestProposeBlockSuccess() {
}

func (s *ProverServerTestSuite) TestGetSignedBlocks() {
latest, err := s.s.rpc.L2.BlockByNumber(context.Background(), nil)
s.Nil(err)
var start uint64
// create 200, we only expect to get 100 back.
for i := 0; i < 200; i++ {
bigInt := big.NewInt(time.Now().UnixNano())
if i == 100 {
start = bigInt.Uint64()
}
signed, err := crypto.Sign(common.BigToHash(bigInt).Bytes(), s.s.proverPrivateKey)
s.Nil(err)
key := db.BuildBlockKey(bigInt.Uint64())

signed, err := crypto.Sign(latest.Hash().Bytes(), s.s.proverPrivateKey)
s.Nil(err)
val := db.BuildBlockValue(common.BigToHash(bigInt).Bytes(), signed, big.NewInt(1))

s.Nil(s.s.db.Put(key, val))
has, err := s.s.db.Has(key)
s.Nil(err)
s.True(has)
}

s.Nil(s.s.db.Put(db.BuildBlockKey(latest.Number().String()), signed))
res := s.sendReq("/signedBlocks")
res := s.sendReq(fmt.Sprintf("/signedBlocks?start=%v", start))
s.Equal(http.StatusOK, res.StatusCode)

signedBlocks := make([]SignedBlock, 0)
Expand All @@ -76,8 +89,5 @@ func (s *ProverServerTestSuite) TestGetSignedBlocks() {
s.Nil(err)
s.Nil(json.Unmarshal(b, &signedBlocks))

s.Equal(1, len(signedBlocks))
s.Equal(latest.Hash().Hex(), signedBlocks[0].BlockHash)
s.Equal(latest.Number().Uint64(), signedBlocks[0].BlockID)
s.Equal(common.Bytes2Hex(signed), signedBlocks[0].Signature)
s.Equal(100, len(signedBlocks))
}
2 changes: 1 addition & 1 deletion prover/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
)

var (
numBlocksToReturn = new(big.Int).SetUint64(200)
defaultNumBlocksToReturn = new(big.Int).SetUint64(100)
)

// @title Taiko Prover API
Expand Down

0 comments on commit eb5bc7a

Please sign in to comment.