diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e5b3c88a..579539709 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/prover/db/db.go b/prover/db/db.go index 1a4707cad..a172a0560 100644 --- a/prover/db/db.go +++ b/prover/db/db.go @@ -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]), + } } diff --git a/prover/db/db_test.go b/prover/db/db_test.go index ced512152..f7cb80509 100644 --- a/prover/db/db_test.go +++ b/prover/db/db_test.go @@ -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)) } diff --git a/prover/prover.go b/prover/prover.go index 94e94a32e..6e41424cc 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -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 @@ -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 } diff --git a/prover/server/api.go b/prover/server/api.go index e6b158100..6a3b380e6 100644 --- a/prover/server/api.go +++ b/prover/server/api.go @@ -1,10 +1,10 @@ package server import ( + "fmt" "math/big" "net/http" "strconv" - "strings" "time" "github.com/ethereum/go-ethereum/common" @@ -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) } diff --git a/prover/server/api_test.go b/prover/server/api_test.go index 2af0b576c..9fc6e31d1 100644 --- a/prover/server/api_test.go +++ b/prover/server/api_test.go @@ -1,9 +1,10 @@ package server import ( - "context" "encoding/json" + "fmt" "io" + "math/big" "net/http" "strings" "time" @@ -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) @@ -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)) } diff --git a/prover/server/server.go b/prover/server/server.go index 14552c138..2b07cbcb0 100644 --- a/prover/server/server.go +++ b/prover/server/server.go @@ -18,7 +18,7 @@ import ( ) var ( - numBlocksToReturn = new(big.Int).SetUint64(200) + defaultNumBlocksToReturn = new(big.Int).SetUint64(100) ) // @title Taiko Prover API