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

feat(prover): change block signing to use timestamp as key #466

Merged
merged 9 commits into from
Dec 7, 2023
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
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