mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-13 07:57:58 -05:00
Compare commits
4 Commits
prealpha-v
...
fix-import
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d783d01f4b | ||
|
|
3c982a9199 | ||
|
|
13bcda11b7 | ||
|
|
632ca10157 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,3 @@
|
||||
.idea
|
||||
assets/params*
|
||||
assets/seed
|
||||
coverage.txt
|
||||
build/bin
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
repos:
|
||||
- repo: git@github.com:scroll-tech/keydead.git
|
||||
rev: main
|
||||
hooks:
|
||||
- id: keydead
|
||||
48
Jenkinsfile
vendored
48
Jenkinsfile
vendored
@@ -13,7 +13,6 @@ pipeline {
|
||||
}
|
||||
environment {
|
||||
GO111MODULE = 'on'
|
||||
// LOG_DOCKER = 'true'
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
@@ -34,14 +33,10 @@ pipeline {
|
||||
export PATH=/home/ubuntu/go/bin:$PATH
|
||||
make dev_docker
|
||||
make -C bridge mock_abi
|
||||
# check compilation
|
||||
make -C bridge bridge
|
||||
make -C coordinator coordinator
|
||||
make -C database db_cli
|
||||
# check docker build
|
||||
make -C bridge docker
|
||||
make -C coordinator coordinator
|
||||
make -C coordinator docker
|
||||
make -C database docker
|
||||
'''
|
||||
}
|
||||
}
|
||||
@@ -62,10 +57,16 @@ pipeline {
|
||||
sh "docker container prune -f"
|
||||
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
|
||||
sh '''
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/database/...
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/bridge/...
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/common/...
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/coordinator/...
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/database
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/database/migrate
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/database/docker
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/bridge/abi
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/bridge/l1
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/bridge/l2
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/bridge/sender
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/common/docker
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/coordinator
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 scroll-tech/coordinator/verifier
|
||||
cd ..
|
||||
'''
|
||||
script {
|
||||
@@ -78,6 +79,33 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Docker') {
|
||||
when {
|
||||
anyOf {
|
||||
changeset "Jenkinsfile"
|
||||
changeset "build/**"
|
||||
changeset "go.work**"
|
||||
changeset "bridge/**"
|
||||
changeset "coordinator/**"
|
||||
changeset "common/**"
|
||||
changeset "database/**"
|
||||
}
|
||||
}
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: "${credentialDocker}", passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
|
||||
script {
|
||||
if (test_result == true) {
|
||||
sh 'docker login --username=${dockerUser} --password=${dockerPassword}'
|
||||
for (i in ['bridge', 'coordinator']) {
|
||||
sh "docker build -t ${imagePrefix}/$i:${GIT_COMMIT} -f build/dockerfiles/${i}.Dockerfile ."
|
||||
sh "docker push ${imagePrefix}/$i:${GIT_COMMIT}"
|
||||
sh "docker rmi ${imagePrefix}/$i:${GIT_COMMIT}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
|
||||
2
Makefile
2
Makefile
@@ -27,7 +27,7 @@ update: ## update dependencies
|
||||
|
||||
dev_docker: ## build docker images for development/testing usages
|
||||
docker build -t scroll_l1geth ./common/docker/l1geth/
|
||||
docker build -t scroll_l2geth ./common/docker/l2geth/
|
||||
docker build -t scroll_l2geth ./common/docker/l2geth/.
|
||||
|
||||
clean: ## Empty out the bin folder
|
||||
@rm -rf build/bin
|
||||
|
||||
@@ -25,7 +25,7 @@ clean: ## Empty out the bin folder
|
||||
@rm -rf build/bin
|
||||
|
||||
docker:
|
||||
DOCKER_BUILDKIT=1 docker build -t scrolltech/${IMAGE_NAME}:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridge.Dockerfile
|
||||
docker build -t scrolltech/${IMAGE_NAME}:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridge.Dockerfile
|
||||
|
||||
docker_push:
|
||||
docker push scrolltech/${IMAGE_NAME}:${IMAGE_VERSION}
|
||||
|
||||
@@ -8,8 +8,6 @@ This repo contains the Scroll bridge.
|
||||
In addition, launching the bridge will launch a separate instance of l2geth, and sets up a communication channel
|
||||
between the two, over JSON-RPC sockets.
|
||||
|
||||
Something we should pay attention is that all private keys inside sender instance cannot be duplicated.
|
||||
|
||||
## Dependency
|
||||
|
||||
+ install `abigen`
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -17,20 +17,29 @@ func TestPackRelayMessageWithProof(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
|
||||
proof := bridge_abi.IL1ScrollMessengerL2MessageProof{
|
||||
BlockHeight: big.NewInt(0),
|
||||
BatchIndex: big.NewInt(0),
|
||||
BlockNumber: big.NewInt(0),
|
||||
MerkleProof: make([]byte, 0),
|
||||
}
|
||||
_, err = l1MessengerABI.Pack("relayMessageWithProof", common.Address{}, common.Address{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), make([]byte, 0), proof)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestPackCommitBatch(t *testing.T) {
|
||||
func TestPackCommitBlock(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
l1RollupABI, err := bridge_abi.RollupMetaData.GetAbi()
|
||||
assert.NoError(err)
|
||||
|
||||
header := bridge_abi.IZKRollupBlockHeader{
|
||||
BlockHash: common.Hash{},
|
||||
ParentHash: common.Hash{},
|
||||
BaseFee: big.NewInt(0),
|
||||
StateRoot: common.Hash{},
|
||||
BlockHeight: 0,
|
||||
GasUsed: 0,
|
||||
Timestamp: 0,
|
||||
ExtraData: make([]byte, 0),
|
||||
}
|
||||
txns := make([]bridge_abi.IZKRollupLayer2Transaction, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
txns[i] = bridge_abi.IZKRollupLayer2Transaction{
|
||||
@@ -41,35 +50,13 @@ func TestPackCommitBatch(t *testing.T) {
|
||||
GasPrice: big.NewInt(0),
|
||||
Value: big.NewInt(0),
|
||||
Data: make([]byte, 0),
|
||||
R: big.NewInt(0),
|
||||
S: big.NewInt(0),
|
||||
V: 0,
|
||||
}
|
||||
}
|
||||
|
||||
header := bridge_abi.IZKRollupLayer2BlockHeader{
|
||||
BlockHash: common.Hash{},
|
||||
ParentHash: common.Hash{},
|
||||
BaseFee: big.NewInt(0),
|
||||
StateRoot: common.Hash{},
|
||||
BlockHeight: 0,
|
||||
GasUsed: 0,
|
||||
Timestamp: 0,
|
||||
ExtraData: make([]byte, 0),
|
||||
Txs: txns,
|
||||
}
|
||||
|
||||
batch := bridge_abi.IZKRollupLayer2Batch{
|
||||
BatchIndex: 0,
|
||||
ParentHash: common.Hash{},
|
||||
Blocks: []bridge_abi.IZKRollupLayer2BlockHeader{header},
|
||||
}
|
||||
|
||||
_, err = l1RollupABI.Pack("commitBatch", batch)
|
||||
_, err = l1RollupABI.Pack("commitBlock", header, txns)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestPackFinalizeBatchWithProof(t *testing.T) {
|
||||
func TestPackFinalizeBlockWithProof(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
l1RollupABI, err := bridge_abi.RollupMetaData.GetAbi()
|
||||
@@ -82,7 +69,7 @@ func TestPackFinalizeBatchWithProof(t *testing.T) {
|
||||
instance[i] = big.NewInt(0)
|
||||
}
|
||||
|
||||
_, err = l1RollupABI.Pack("finalizeBatchWithProof", common.Hash{}, proof, instance)
|
||||
_, err = l1RollupABI.Pack("finalizeBlockWithProof", common.Hash{}, proof, instance)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"scroll-tech/bridge/config"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/migrate"
|
||||
)
|
||||
|
||||
func initDB(file string) (*sqlx.DB, error) {
|
||||
dbCfg, err := database.NewConfig(file)
|
||||
cfg, err := config.NewConfig(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbCfg := cfg.DBConfig
|
||||
factory, err := database.NewOrmFactory(dbCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -25,9 +27,9 @@ func initDB(file string) (*sqlx.DB, error) {
|
||||
return factory.GetDB(), nil
|
||||
}
|
||||
|
||||
// resetDB clean or reset database.
|
||||
func resetDB(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(utils.ConfigFileFlag.Name))
|
||||
// ResetDB clean or reset database.
|
||||
func ResetDB(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(configFileFlag.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,9 +44,9 @@ func resetDB(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDBStatus check db status
|
||||
func checkDBStatus(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(utils.ConfigFileFlag.Name))
|
||||
// CheckDBStatus check db status
|
||||
func CheckDBStatus(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(configFileFlag.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -52,9 +54,9 @@ func checkDBStatus(ctx *cli.Context) error {
|
||||
return migrate.Status(db.DB)
|
||||
}
|
||||
|
||||
// dbVersion return the latest version
|
||||
func dbVersion(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(utils.ConfigFileFlag.Name))
|
||||
// DBVersion return the latest version
|
||||
func DBVersion(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(configFileFlag.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,9 +67,9 @@ func dbVersion(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// migrateDB migrate db
|
||||
func migrateDB(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(utils.ConfigFileFlag.Name))
|
||||
// MigrateDB migrate db
|
||||
func MigrateDB(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(configFileFlag.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,9 +77,9 @@ func migrateDB(ctx *cli.Context) error {
|
||||
return migrate.Migrate(db.DB)
|
||||
}
|
||||
|
||||
// rollbackDB rollback db by version
|
||||
func rollbackDB(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(utils.ConfigFileFlag.Name))
|
||||
// RollbackDB rollback db by version
|
||||
func RollbackDB(ctx *cli.Context) error {
|
||||
db, err := initDB(ctx.String(configFileFlag.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3,6 +3,40 @@ package main
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var (
|
||||
commonFlags = []cli.Flag{
|
||||
&configFileFlag,
|
||||
&verbosityFlag,
|
||||
&logFileFlag,
|
||||
&logJSONFormat,
|
||||
&logDebugFlag,
|
||||
}
|
||||
// configFileFlag load json type config file.
|
||||
configFileFlag = cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "JSON configuration file",
|
||||
Value: "./config.json",
|
||||
}
|
||||
// verbosityFlag log level.
|
||||
verbosityFlag = cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail",
|
||||
Value: 3,
|
||||
}
|
||||
// logFileFlag decides where the logger output is sent. If this flag is left
|
||||
// empty, it will log to stdout.
|
||||
logFileFlag = cli.StringFlag{
|
||||
Name: "log.file",
|
||||
Usage: "Tells the sequencer where to write log entries",
|
||||
}
|
||||
logJSONFormat = cli.BoolFlag{
|
||||
Name: "log.json",
|
||||
Usage: "Tells the sequencer whether log format is json or not",
|
||||
Value: true,
|
||||
}
|
||||
logDebugFlag = cli.BoolFlag{
|
||||
Name: "log.debug",
|
||||
Usage: "Prepends log messages with call-site location (file and line number)",
|
||||
}
|
||||
apiFlags = []cli.Flag{
|
||||
&httpEnabledFlag,
|
||||
&httpListenAddrFlag,
|
||||
|
||||
@@ -9,10 +9,9 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"scroll-tech/database"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
"scroll-tech/common/version"
|
||||
"scroll-tech/database"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
"scroll-tech/bridge/l1"
|
||||
@@ -27,19 +26,67 @@ func main() {
|
||||
app.Name = "bridge"
|
||||
app.Usage = "The Scroll Bridge"
|
||||
app.Version = version.Version
|
||||
app.Flags = append(app.Flags, utils.CommonFlags...)
|
||||
app.Flags = append(app.Flags, commonFlags...)
|
||||
app.Flags = append(app.Flags, apiFlags...)
|
||||
app.Flags = append(app.Flags, l1Flags...)
|
||||
app.Flags = append(app.Flags, l2Flags...)
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
return utils.Setup(&utils.LogConfig{
|
||||
LogFile: ctx.String(utils.LogFileFlag.Name),
|
||||
LogJSONFormat: ctx.Bool(utils.LogJSONFormat.Name),
|
||||
LogDebug: ctx.Bool(utils.LogDebugFlag.Name),
|
||||
Verbosity: ctx.Int(utils.VerbosityFlag.Name),
|
||||
LogFile: ctx.String(logFileFlag.Name),
|
||||
LogJSONFormat: ctx.Bool(logJSONFormat.Name),
|
||||
LogDebug: ctx.Bool(logDebugFlag.Name),
|
||||
Verbosity: ctx.Int(verbosityFlag.Name),
|
||||
})
|
||||
}
|
||||
app.Commands = []*cli.Command{
|
||||
{
|
||||
Name: "reset",
|
||||
Usage: "Clean and reset database.",
|
||||
Action: ResetDB,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "Check migration status.",
|
||||
Action: CheckDBStatus,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Display the current database version.",
|
||||
Action: DBVersion,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "migrate",
|
||||
Usage: "Migrate the database to the latest version.",
|
||||
Action: MigrateDB,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rollback",
|
||||
Usage: "Roll back the database to a previous <version>. Rolls back a single migration if no version specified.",
|
||||
Action: RollbackDB,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
&cli.IntFlag{
|
||||
Name: "version",
|
||||
Usage: "Rollback to the specified version.",
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Run the sequencer.
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
@@ -64,7 +111,7 @@ func applyConfig(ctx *cli.Context, cfg *config.Config) {
|
||||
|
||||
func action(ctx *cli.Context) error {
|
||||
// Load config file.
|
||||
cfgFile := ctx.String(utils.ConfigFileFlag.Name)
|
||||
cfgFile := ctx.String(configFileFlag.Name)
|
||||
cfg, err := config.NewConfig(cfgFile)
|
||||
if err != nil {
|
||||
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
|
||||
|
||||
@@ -15,12 +15,9 @@
|
||||
"escalate_multiple_num": 11,
|
||||
"escalate_multiple_den": 10,
|
||||
"max_gas_price": 10000000000,
|
||||
"tx_type": "AccessListTx",
|
||||
"min_balance": 100000000000000000000
|
||||
"tx_type": "AccessListTx"
|
||||
},
|
||||
"message_sender_private_keys": [
|
||||
"1212121212121212121212121212121212121212121212121212121212121212"
|
||||
]
|
||||
"private_key": "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"
|
||||
}
|
||||
},
|
||||
"l2_config": {
|
||||
@@ -44,15 +41,9 @@
|
||||
"escalate_multiple_num": 11,
|
||||
"escalate_multiple_den": 10,
|
||||
"max_gas_price": 10000000000,
|
||||
"tx_type": "DynamicFeeTx",
|
||||
"min_balance": 100000000000000000000
|
||||
"tx_type": "DynamicFeeTx"
|
||||
},
|
||||
"message_sender_private_keys": [
|
||||
"1212121212121212121212121212121212121212121212121212121212121212"
|
||||
],
|
||||
"roller_sender_private_keys": [
|
||||
"1212121212121212121212121212121212121212121212121212121212121212"
|
||||
]
|
||||
"private_key": "1212121212121212121212121212121212121212121212121212121212121212"
|
||||
}
|
||||
},
|
||||
"db_config": {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
|
||||
@@ -32,10 +28,8 @@ type SenderConfig struct {
|
||||
EscalateMultipleDen uint64 `json:"escalate_multiple_den"`
|
||||
// The maximum gas price can be used to send transaction.
|
||||
MaxGasPrice uint64 `json:"max_gas_price"`
|
||||
// The transaction type to use: LegacyTx, AccessListTx, DynamicFeeTx
|
||||
// the transaction type to use: LegacyTx, AccessListTx, DynamicFeeTx
|
||||
TxType string `json:"tx_type"`
|
||||
// The min balance set for check and set balance for sender's accounts.
|
||||
MinBalance *big.Int `json:"min_balance,omitempty"`
|
||||
}
|
||||
|
||||
// L1Config loads l1eth configuration items.
|
||||
@@ -67,37 +61,12 @@ type L2Config struct {
|
||||
// Proof generation frequency, generating proof every k blocks
|
||||
ProofGenerationFreq uint64 `json:"proof_generation_freq"`
|
||||
// Skip generating proof when that opcodes appeared
|
||||
SkippedOpcodes map[string]struct{} `json:"-"`
|
||||
SkippedOpcodes []string `json:"skipped_opcodes"`
|
||||
// The relayer config
|
||||
RelayerConfig *RelayerConfig `json:"relayer_config"`
|
||||
}
|
||||
|
||||
// L2ConfigAlias L2Config alias name, designed just for unmarshal.
|
||||
type L2ConfigAlias L2Config
|
||||
|
||||
// UnmarshalJSON unmarshal l2config.
|
||||
func (l2 *L2Config) UnmarshalJSON(input []byte) error {
|
||||
var jsonConfig struct {
|
||||
L2ConfigAlias
|
||||
SkippedOpcodes []string `json:"skipped_opcodes"`
|
||||
}
|
||||
if err := json.Unmarshal(input, &jsonConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
*l2 = L2Config(jsonConfig.L2ConfigAlias)
|
||||
l2.SkippedOpcodes = make(map[string]struct{}, len(jsonConfig.SkippedOpcodes))
|
||||
for _, opcode := range jsonConfig.SkippedOpcodes {
|
||||
l2.SkippedOpcodes[opcode] = struct{}{}
|
||||
}
|
||||
if 0 == l2.ProofGenerationFreq {
|
||||
l2.ProofGenerationFreq = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelayerConfig loads relayer configuration items.
|
||||
// What we need to pay attention to is that
|
||||
// `MessageSenderPrivateKeys` and `RollupSenderPrivateKeys` cannot have common private keys.
|
||||
type RelayerConfig struct {
|
||||
// RollupContractAddress store the rollup contract address.
|
||||
RollupContractAddress common.Address `json:"rollup_contract_address,omitempty"`
|
||||
@@ -106,45 +75,7 @@ type RelayerConfig struct {
|
||||
// sender config
|
||||
SenderConfig *SenderConfig `json:"sender_config"`
|
||||
// The private key of the relayer
|
||||
MessageSenderPrivateKeys []*ecdsa.PrivateKey `json:"-"`
|
||||
RollupSenderPrivateKeys []*ecdsa.PrivateKey `json:"-"`
|
||||
}
|
||||
|
||||
// RelayerConfigAlias RelayerConfig alias name
|
||||
type RelayerConfigAlias RelayerConfig
|
||||
|
||||
// UnmarshalJSON unmarshal relayer_config struct.
|
||||
func (r *RelayerConfig) UnmarshalJSON(input []byte) error {
|
||||
var jsonConfig struct {
|
||||
RelayerConfigAlias
|
||||
// The private key of the relayer
|
||||
MessageSenderPrivateKeys []string `json:"message_sender_private_keys"`
|
||||
RollupSenderPrivateKeys []string `json:"roller_sender_private_keys,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(input, &jsonConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get messenger private key list.
|
||||
*r = RelayerConfig(jsonConfig.RelayerConfigAlias)
|
||||
for _, privStr := range jsonConfig.MessageSenderPrivateKeys {
|
||||
priv, err := crypto.ToECDSA(common.FromHex(privStr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("incorrect private_key_list format, err: %v", err)
|
||||
}
|
||||
r.MessageSenderPrivateKeys = append(r.MessageSenderPrivateKeys, priv)
|
||||
}
|
||||
|
||||
// Get rollup private key
|
||||
for _, privStr := range jsonConfig.RollupSenderPrivateKeys {
|
||||
priv, err := crypto.ToECDSA(common.FromHex(privStr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("incorrect roller_private_key format, err: %v", err)
|
||||
}
|
||||
r.RollupSenderPrivateKeys = append(r.RollupSenderPrivateKeys, priv)
|
||||
}
|
||||
|
||||
return nil
|
||||
PrivateKey string `json:"private_key"`
|
||||
}
|
||||
|
||||
// Config load configuration items.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.True(t, assert.NoError(t, err), "failed to load config")
|
||||
assert.True(t, len(cfg.L2Config.SkippedOpcodes) == 2)
|
||||
assert.True(t, cfg.L2Config.ProofGenerationFreq == 1)
|
||||
assert.True(t, len(cfg.L1Config.RelayerConfig.MessageSenderPrivateKeys) > 0)
|
||||
assert.True(t, len(cfg.L2Config.RelayerConfig.MessageSenderPrivateKeys) > 0)
|
||||
assert.True(t, len(cfg.L2Config.RelayerConfig.RollupSenderPrivateKeys) > 0)
|
||||
}
|
||||
@@ -3,8 +3,11 @@ module scroll-tech/bridge
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/ethereum/go-ethereum v1.10.13
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
@@ -15,19 +18,18 @@ require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.13 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.0 // indirect
|
||||
github.com/iden3/go-iden3-crypto v0.0.12 // indirect
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.14 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rjeczalik/notify v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/scroll-tech/zktrie v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
|
||||
@@ -136,6 +136,8 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@@ -224,6 +226,8 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -256,6 +260,9 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
|
||||
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
@@ -276,6 +283,9 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
@@ -337,10 +347,8 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6 h1:o0Gq2d8nus6x82apluA5RJwjlOca7LIlpAfxlyvQvxs=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6/go.mod h1:jurIpDQ0hqtp9//xxeWzr8X9KMP/+TYn+vz3K1wZrv0=
|
||||
github.com/scroll-tech/zktrie v0.3.0 h1:c0GRNELUyAtyuiwllQKT1XjNbs7NRGfguKouiyLfFNY=
|
||||
github.com/scroll-tech/zktrie v0.3.0/go.mod h1:CuJFlG1/soTJJBAySxCZgTF7oPvd5qF6utHOEciC43Q=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d h1:eh1i1M9BKPCQckNFQsV/yfazQ895hevkWr8GuqhKNrk=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d/go.mod h1:SkQ1431r0LkrExTELsapw6JQHYpki8O1mSsObTSmBWU=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
|
||||
@@ -18,11 +18,11 @@ type Backend struct {
|
||||
cfg *config.L1Config
|
||||
watcher *Watcher
|
||||
relayer *Layer1Relayer
|
||||
orm orm.L1MessageOrm
|
||||
orm orm.Layer1MessageOrm
|
||||
}
|
||||
|
||||
// New returns a new instance of Backend.
|
||||
func New(ctx context.Context, cfg *config.L1Config, orm orm.L1MessageOrm) (*Backend, error) {
|
||||
func New(ctx context.Context, cfg *config.L1Config, orm orm.Layer1MessageOrm) (*Backend, error) {
|
||||
client, err := ethclient.Dial(cfg.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,7 +2,6 @@ package l1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
// Layer1Relayer is responsible for
|
||||
// 1. fetch pending L1Message from db
|
||||
// 1. fetch pending Layer1Message from db
|
||||
// 2. relay pending message to layer 2 node
|
||||
//
|
||||
// Actions are triggered by new head from layer 1 geth node.
|
||||
@@ -31,7 +31,7 @@ type Layer1Relayer struct {
|
||||
client *ethclient.Client
|
||||
sender *sender.Sender
|
||||
|
||||
db orm.L1MessageOrm
|
||||
db orm.Layer1MessageOrm
|
||||
cfg *config.RelayerConfig
|
||||
|
||||
// channel used to communicate with transaction sender
|
||||
@@ -42,14 +42,20 @@ type Layer1Relayer struct {
|
||||
}
|
||||
|
||||
// NewLayer1Relayer will return a new instance of Layer1RelayerClient
|
||||
func NewLayer1Relayer(ctx context.Context, ethClient *ethclient.Client, l1ConfirmNum int64, db orm.L1MessageOrm, cfg *config.RelayerConfig) (*Layer1Relayer, error) {
|
||||
func NewLayer1Relayer(ctx context.Context, ethClient *ethclient.Client, l1ConfirmNum int64, db orm.Layer1MessageOrm, cfg *config.RelayerConfig) (*Layer1Relayer, error) {
|
||||
l2MessengerABI, err := bridge_abi.L2MessengerMetaData.GetAbi()
|
||||
if err != nil {
|
||||
log.Warn("new L2MessengerABI failed", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sender, err := sender.NewSender(ctx, cfg.SenderConfig, cfg.MessageSenderPrivateKeys)
|
||||
prv, err := crypto.HexToECDSA(cfg.PrivateKey)
|
||||
if err != nil {
|
||||
log.Error("Failed to import private key from config file")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sender, err := sender.NewSender(ctx, cfg.SenderConfig, prv)
|
||||
if err != nil {
|
||||
log.Error("new sender failed", "err", err)
|
||||
return nil, err
|
||||
@@ -75,19 +81,12 @@ func (r *Layer1Relayer) ProcessSavedEvents() {
|
||||
log.Error("Failed to fetch unprocessed L1 messages", "err", err)
|
||||
return
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
if err = r.processSavedEvent(msg); err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("failed to process event", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Layer1Relayer) processSavedEvent(msg *orm.L1Message) error {
|
||||
msg := msgs[0]
|
||||
// @todo add support to relay multiple messages
|
||||
from := common.HexToAddress(msg.Sender)
|
||||
sender := common.HexToAddress(msg.Sender)
|
||||
target := common.HexToAddress(msg.Target)
|
||||
value, ok := big.NewInt(0).SetString(msg.Value, 10)
|
||||
if !ok {
|
||||
@@ -99,16 +98,17 @@ func (r *Layer1Relayer) processSavedEvent(msg *orm.L1Message) error {
|
||||
deadline := big.NewInt(int64(msg.Deadline))
|
||||
msgNonce := big.NewInt(int64(msg.Nonce))
|
||||
calldata := common.Hex2Bytes(msg.Calldata)
|
||||
data, err := r.l2MessengerABI.Pack("relayMessage", from, target, value, fee, deadline, msgNonce, calldata)
|
||||
data, err := r.l2MessengerABI.Pack("relayMessage", sender, target, value, fee, deadline, msgNonce, calldata)
|
||||
if err != nil {
|
||||
log.Error("Failed to pack relayMessage", "msg.nonce", msg.Nonce, "msg.height", msg.Height, "err", err)
|
||||
// TODO: need to skip this message by changing its status to MsgError
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := r.sender.SendTransaction(msg.Layer1Hash, &r.cfg.MessengerContractAddress, big.NewInt(0), data)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Error("Failed to send relayMessage tx to L2", "msg.nonce", msg.Nonce, "msg.height", msg.Height, "err", err)
|
||||
return
|
||||
}
|
||||
log.Info("relayMessage to layer2", "layer1 hash", msg.Layer1Hash, "tx hash", hash)
|
||||
|
||||
@@ -116,7 +116,6 @@ func (r *Layer1Relayer) processSavedEvent(msg *orm.L1Message) error {
|
||||
if err != nil {
|
||||
log.Error("UpdateLayer1StatusAndLayer2Hash failed", "msg.layer1hash", msg.Layer1Hash, "msg.height", msg.Height, "err", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the relayer process
|
||||
|
||||
@@ -7,41 +7,52 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/database/migrate"
|
||||
"scroll-tech/database"
|
||||
|
||||
"scroll-tech/bridge/mock"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
"scroll-tech/bridge/l1"
|
||||
|
||||
"scroll-tech/database"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
"scroll-tech/common/utils"
|
||||
)
|
||||
|
||||
var TEST_CONFIG = &mock.TestConfig{
|
||||
L1GethTestConfig: mock.L1GethTestConfig{
|
||||
HPort: 0,
|
||||
WPort: 8570,
|
||||
},
|
||||
DbTestconfig: mock.DbTestconfig{
|
||||
DbName: "testl1relayer_db",
|
||||
DbPort: 5440,
|
||||
DB_CONFIG: &database.DBConfig{
|
||||
DriverName: utils.GetEnvWithDefault("TEST_DB_DRIVER", "postgres"),
|
||||
DSN: utils.GetEnvWithDefault("TEST_DB_DSN", "postgres://postgres:123456@localhost:5440/testl1relayer_db?sslmode=disable"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TestCreateNewRelayer test create new relayer instance and stop
|
||||
func TestCreateNewL1Relayer(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
l1docker := docker.NewTestL1Docker(t)
|
||||
l1docker := mock.NewTestL1Docker(t, TEST_CONFIG)
|
||||
defer l1docker.Stop()
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1docker.Endpoint()
|
||||
cfg.L1Config.Endpoint = l1docker.Endpoint()
|
||||
|
||||
client, err := ethclient.Dial(l1docker.Endpoint())
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbImg := docker.NewTestDBDocker(t, cfg.DBConfig.DriverName)
|
||||
dbImg := mock.GetDbDocker(t, TEST_CONFIG)
|
||||
dbImg.Start()
|
||||
defer dbImg.Stop()
|
||||
cfg.DBConfig.DSN = dbImg.Endpoint()
|
||||
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
db, err := database.NewOrmFactory(TEST_CONFIG.DB_CONFIG)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
|
||||
relayer, err := l1.NewLayer1Relayer(context.Background(), client, 1, db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
defer relayer.Stop()
|
||||
|
||||
relayer.Start()
|
||||
|
||||
defer relayer.Stop()
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
type Watcher struct {
|
||||
ctx context.Context
|
||||
client *ethclient.Client
|
||||
db orm.L1MessageOrm
|
||||
db orm.Layer1MessageOrm
|
||||
|
||||
// The number of new blocks to wait for a block to be confirmed
|
||||
confirmations uint64
|
||||
@@ -40,7 +40,7 @@ type Watcher struct {
|
||||
|
||||
// NewWatcher returns a new instance of Watcher. The instance will be not fully prepared,
|
||||
// and still needs to be finalized and ran by calling `watcher.Start`.
|
||||
func NewWatcher(ctx context.Context, client *ethclient.Client, startHeight uint64, confirmations uint64, messengerAddress common.Address, messengerABI *abi.ABI, db orm.L1MessageOrm) *Watcher {
|
||||
func NewWatcher(ctx context.Context, client *ethclient.Client, startHeight uint64, confirmations uint64, messengerAddress common.Address, messengerABI *abi.ABI, db orm.Layer1MessageOrm) *Watcher {
|
||||
savedHeight, err := db.GetLayer1LatestWatchedHeight()
|
||||
if err != nil {
|
||||
log.Warn("Failed to fetch height from db", "err", err)
|
||||
@@ -93,8 +93,6 @@ func (r *Watcher) Stop() {
|
||||
r.stop <- true
|
||||
}
|
||||
|
||||
const contractEventsBlocksFetchLimit = int64(10)
|
||||
|
||||
// FetchContractEvent pull latest event logs from given contract address and save in DB
|
||||
func (r *Watcher) fetchContractEvent(blockHeight uint64) error {
|
||||
fromBlock := int64(r.processedMsgHeight) + 1
|
||||
@@ -104,10 +102,6 @@ func (r *Watcher) fetchContractEvent(blockHeight uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if toBlock > fromBlock+contractEventsBlocksFetchLimit {
|
||||
toBlock = fromBlock + contractEventsBlocksFetchLimit - 1
|
||||
}
|
||||
|
||||
// warning: uint int conversion...
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(fromBlock), // inclusive
|
||||
@@ -137,18 +131,18 @@ func (r *Watcher) fetchContractEvent(blockHeight uint64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.db.SaveL1Messages(r.ctx, eventLogs)
|
||||
err = r.db.SaveLayer1Messages(r.ctx, eventLogs)
|
||||
if err == nil {
|
||||
r.processedMsgHeight = uint64(toBlock)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseBridgeEventLogs(logs []types.Log, messengerABI *abi.ABI) ([]*orm.L1Message, error) {
|
||||
func parseBridgeEventLogs(logs []types.Log, messengerABI *abi.ABI) ([]*orm.Layer1Message, error) {
|
||||
// Need use contract abi to parse event Log
|
||||
// Can only be tested after we have our contracts set up
|
||||
|
||||
var parsedlogs []*orm.L1Message
|
||||
var parsedlogs []*orm.Layer1Message
|
||||
for _, vLog := range logs {
|
||||
event := struct {
|
||||
Target common.Address
|
||||
@@ -168,7 +162,7 @@ func parseBridgeEventLogs(logs []types.Log, messengerABI *abi.ABI) ([]*orm.L1Mes
|
||||
}
|
||||
// target is in topics[1]
|
||||
event.Target = common.HexToAddress(vLog.Topics[1].String())
|
||||
parsedlogs = append(parsedlogs, &orm.L1Message{
|
||||
parsedlogs = append(parsedlogs, &orm.Layer1Message{
|
||||
Nonce: event.MessageNonce.Uint64(),
|
||||
Height: vLog.BlockNumber,
|
||||
Sender: event.Sender.String(),
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
|
||||
"scroll-tech/database"
|
||||
|
||||
bridge_abi "scroll-tech/bridge/abi"
|
||||
"scroll-tech/bridge/config"
|
||||
)
|
||||
|
||||
@@ -28,12 +30,29 @@ func New(ctx context.Context, cfg *config.L2Config, orm database.OrmFactory) (*B
|
||||
return nil, err
|
||||
}
|
||||
|
||||
relayer, err := NewLayer2Relayer(ctx, client, cfg.ProofGenerationFreq, cfg.SkippedOpcodes, int64(cfg.Confirmations), orm, cfg.RelayerConfig)
|
||||
l2MessengerABI, err := bridge_abi.L2MessengerMetaData.GetAbi()
|
||||
if err != nil {
|
||||
log.Warn("new L2MessengerABI failed", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skippedOpcodes := make(map[string]struct{}, len(cfg.SkippedOpcodes))
|
||||
for _, op := range cfg.SkippedOpcodes {
|
||||
skippedOpcodes[op] = struct{}{}
|
||||
}
|
||||
|
||||
proofGenerationFreq := cfg.ProofGenerationFreq
|
||||
if proofGenerationFreq == 0 {
|
||||
log.Warn("receive 0 proof_generation_freq, change to 1")
|
||||
proofGenerationFreq = 1
|
||||
}
|
||||
|
||||
relayer, err := NewLayer2Relayer(ctx, client, proofGenerationFreq, skippedOpcodes, int64(cfg.Confirmations), orm, cfg.RelayerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l2Watcher := NewL2WatcherClient(ctx, client, cfg.Confirmations, cfg.ProofGenerationFreq, cfg.SkippedOpcodes, cfg.L2MessengerAddress, orm)
|
||||
l2Watcher := NewL2WatcherClient(ctx, client, cfg.Confirmations, proofGenerationFreq, skippedOpcodes, cfg.L2MessengerAddress, l2MessengerABI, orm)
|
||||
|
||||
return &Backend{
|
||||
cfg: cfg,
|
||||
@@ -68,7 +87,7 @@ func (l2 *Backend) APIs() []rpc.API {
|
||||
}
|
||||
}
|
||||
|
||||
// MockBlockTrace for test case
|
||||
func (l2 *Backend) MockBlockTrace(blockTrace *types.BlockTrace) {
|
||||
l2.l2Watcher.Send(blockTrace)
|
||||
// MockBlockResult for test case
|
||||
func (l2 *Backend) MockBlockResult(blockResult *types.BlockResult) {
|
||||
l2.l2Watcher.Send(blockResult)
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package l2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
"scroll-tech/database/orm"
|
||||
)
|
||||
|
||||
// batch-related config
|
||||
const (
|
||||
batchTimeSec = uint64(5 * 60) // 5min
|
||||
batchGasThreshold = uint64(3_000_000)
|
||||
batchBlocksLimit = uint64(100)
|
||||
)
|
||||
|
||||
// TODO:
|
||||
// + generate batch parallelly
|
||||
// + TraceHasUnsupportedOpcodes
|
||||
// + proofGenerationFreq
|
||||
func (w *WatcherClient) tryProposeBatch() error {
|
||||
w.bpMutex.Lock()
|
||||
defer w.bpMutex.Unlock()
|
||||
|
||||
blocks, err := w.orm.GetUnbatchedBlocks(
|
||||
map[string]interface{}{},
|
||||
fmt.Sprintf("order by number ASC LIMIT %d", batchBlocksLimit),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if blocks[0].GasUsed > batchGasThreshold {
|
||||
log.Warn("gas overflow even for only 1 block", "gas", blocks[0].GasUsed)
|
||||
return w.createBatchForBlocks(blocks[:1])
|
||||
}
|
||||
|
||||
var (
|
||||
length = len(blocks)
|
||||
gasUsed uint64
|
||||
)
|
||||
// add blocks into batch until reach batchGasThreshold
|
||||
for i, block := range blocks {
|
||||
if gasUsed+block.GasUsed > batchGasThreshold {
|
||||
blocks = blocks[:i]
|
||||
break
|
||||
}
|
||||
gasUsed += block.GasUsed
|
||||
}
|
||||
|
||||
// if too few gas gathered, but we don't want to halt, we then check the first block in the batch:
|
||||
// if it's not old enough we will skip proposing the batch,
|
||||
// otherwise we will still propose a batch
|
||||
if length == len(blocks) && blocks[0].BlockTimestamp+batchTimeSec > uint64(time.Now().Unix()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return w.createBatchForBlocks(blocks)
|
||||
}
|
||||
|
||||
func (w *WatcherClient) createBatchForBlocks(blocks []*orm.BlockInfo) error {
|
||||
dbTx, err := w.orm.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dbTxErr error
|
||||
defer func() {
|
||||
if dbTxErr != nil {
|
||||
if err := dbTx.Rollback(); err != nil {
|
||||
log.Error("dbTx.Rollback()", "err", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
batchID string
|
||||
startBlock = blocks[0]
|
||||
endBlock = blocks[len(blocks)-1]
|
||||
txNum, gasUsed uint64
|
||||
blockIDs = make([]uint64, len(blocks))
|
||||
)
|
||||
for i, block := range blocks {
|
||||
txNum += block.TxNum
|
||||
gasUsed += block.GasUsed
|
||||
blockIDs[i] = block.Number
|
||||
}
|
||||
|
||||
batchID, dbTxErr = w.orm.NewBatchInDBTx(dbTx, startBlock, endBlock, startBlock.ParentHash, txNum, gasUsed)
|
||||
if dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
|
||||
if dbTxErr = w.orm.SetBatchIDForBlocksInDBTx(dbTx, blockIDs, batchID); dbTxErr != nil {
|
||||
return dbTxErr
|
||||
}
|
||||
|
||||
dbTxErr = dbTx.Commit()
|
||||
return dbTxErr
|
||||
}
|
||||
@@ -10,8 +10,7 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
)
|
||||
|
||||
//nolint:unused
|
||||
func blockTraceIsValid(trace *types.BlockTrace) bool {
|
||||
func blockTraceIsValid(trace *types.BlockResult) bool {
|
||||
if trace == nil {
|
||||
log.Warn("block trace is empty")
|
||||
return false
|
||||
@@ -23,7 +22,6 @@ func blockTraceIsValid(trace *types.BlockTrace) bool {
|
||||
return flag
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func structLogResIsValid(txLogs []*types.StructLogRes) bool {
|
||||
res := true
|
||||
for i := 0; i < len(txLogs); i++ {
|
||||
@@ -50,7 +48,6 @@ func structLogResIsValid(txLogs []*types.StructLogRes) bool {
|
||||
return res
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func codeIsValid(txLog *types.StructLogRes, n int) bool {
|
||||
extraData := txLog.ExtraData
|
||||
if extraData == nil {
|
||||
@@ -63,7 +60,6 @@ func codeIsValid(txLog *types.StructLogRes, n int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func stateIsValid(txLog *types.StructLogRes, n int) bool {
|
||||
extraData := txLog.ExtraData
|
||||
if extraData == nil {
|
||||
@@ -77,7 +73,7 @@ func stateIsValid(txLog *types.StructLogRes, n int) bool {
|
||||
}
|
||||
|
||||
// TraceHasUnsupportedOpcodes check if exist unsupported opcodes
|
||||
func TraceHasUnsupportedOpcodes(opcodes map[string]struct{}, trace *types.BlockTrace) bool {
|
||||
func TraceHasUnsupportedOpcodes(opcodes map[string]struct{}, trace *types.BlockResult) bool {
|
||||
if trace == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package l2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
)
|
||||
|
||||
var (
|
||||
// config
|
||||
cfg *config.Config
|
||||
|
||||
// docker consider handler.
|
||||
l1gethImg docker.ImgInstance
|
||||
l2gethImg docker.ImgInstance
|
||||
dbImg docker.ImgInstance
|
||||
|
||||
// l2geth client
|
||||
l2Cli *ethclient.Client
|
||||
)
|
||||
|
||||
func setupEnv(t *testing.T) (err error) {
|
||||
// Load config.
|
||||
cfg, err = config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create l1geth container.
|
||||
l1gethImg = docker.NewTestL1Docker(t)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1gethImg.Endpoint()
|
||||
cfg.L1Config.Endpoint = l1gethImg.Endpoint()
|
||||
|
||||
// Create l2geth container.
|
||||
l2gethImg = docker.NewTestL2Docker(t)
|
||||
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint = l2gethImg.Endpoint()
|
||||
cfg.L2Config.Endpoint = l2gethImg.Endpoint()
|
||||
|
||||
// Create db container.
|
||||
dbImg = docker.NewTestDBDocker(t, cfg.DBConfig.DriverName)
|
||||
cfg.DBConfig.DSN = dbImg.Endpoint()
|
||||
|
||||
// Create l2geth client.
|
||||
l2Cli, err = ethclient.Dial(cfg.L2Config.Endpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func free(t *testing.T) {
|
||||
if dbImg != nil {
|
||||
assert.NoError(t, dbImg.Stop())
|
||||
}
|
||||
if l1gethImg != nil {
|
||||
assert.NoError(t, l1gethImg.Stop())
|
||||
}
|
||||
if l2gethImg != nil {
|
||||
assert.NoError(t, l2gethImg.Stop())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
if err := setupEnv(t); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Run l2 watcher test cases.
|
||||
t.Run("TestCreateNewWatcherAndStop", testCreateNewWatcherAndStop)
|
||||
t.Run("TestMonitorBridgeContract", testMonitorBridgeContract)
|
||||
t.Run("TestFetchMultipleSentMessageInOneBlock", testFetchMultipleSentMessageInOneBlock)
|
||||
t.Run("TestTraceHasUnsupportedOpcodes", testTraceHasUnsupportedOpcodes)
|
||||
|
||||
// Run l2 relayer test cases.
|
||||
t.Run("TestCreateNewRelayer", testCreateNewRelayer)
|
||||
t.Run("TestL2RelayerProcessSaveEvents", testL2RelayerProcessSaveEvents)
|
||||
t.Run("testL2RelayerProcessPendingBatches", testL2RelayerProcessPendingBatches)
|
||||
t.Run("testL2RelayerProcessCommittedBatches", testL2RelayerProcessCommittedBatches)
|
||||
|
||||
t.Cleanup(func() {
|
||||
free(t)
|
||||
})
|
||||
}
|
||||
@@ -2,14 +2,16 @@ package l2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
// not sure if this will make problems when relay with l1geth
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
@@ -30,6 +32,7 @@ import (
|
||||
type Layer2Relayer struct {
|
||||
ctx context.Context
|
||||
client *ethclient.Client
|
||||
sender *sender.Sender
|
||||
|
||||
proofGenerationFreq uint64
|
||||
skippedOpcodes map[string]struct{}
|
||||
@@ -37,58 +40,66 @@ type Layer2Relayer struct {
|
||||
db database.OrmFactory
|
||||
cfg *config.RelayerConfig
|
||||
|
||||
messageSender *sender.Sender
|
||||
messageCh <-chan *sender.Confirmation
|
||||
l1MessengerABI *abi.ABI
|
||||
|
||||
rollupSender *sender.Sender
|
||||
rollupCh <-chan *sender.Confirmation
|
||||
l1RollupABI *abi.ABI
|
||||
l1RollupABI *abi.ABI
|
||||
|
||||
// a list of processing message, indexed by layer2 hash
|
||||
processingMessage map[string]string
|
||||
|
||||
// a list of processing batch commitment, indexed by batch id
|
||||
processingCommitment map[string]string
|
||||
// a list of processing block, indexed by block height
|
||||
processingBlock map[string]uint64
|
||||
|
||||
// a list of processing batch finalization, indexed by batch id
|
||||
processingFinalization map[string]string
|
||||
// a list of processing proof, indexed by block height
|
||||
processingProof map[string]uint64
|
||||
|
||||
stopCh chan struct{}
|
||||
// channel used to communicate with transaction sender
|
||||
confirmationCh <-chan *sender.Confirmation
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// NewLayer2Relayer will return a new instance of Layer2RelayerClient
|
||||
func NewLayer2Relayer(ctx context.Context, ethClient *ethclient.Client, proofGenFreq uint64, skippedOpcodes map[string]struct{}, l2ConfirmNum int64, db database.OrmFactory, cfg *config.RelayerConfig) (*Layer2Relayer, error) {
|
||||
// @todo use different sender for relayer, block commit and proof finalize
|
||||
messageSender, err := sender.NewSender(ctx, cfg.SenderConfig, cfg.MessageSenderPrivateKeys)
|
||||
|
||||
l1MessengerABI, err := bridge_abi.L1MessengerMetaData.GetAbi()
|
||||
if err != nil {
|
||||
log.Error("Failed to create messenger sender", "err", err)
|
||||
log.Error("Get L1MessengerABI failed", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rollupSender, err := sender.NewSender(ctx, cfg.SenderConfig, cfg.RollupSenderPrivateKeys)
|
||||
l1RollupABI, err := bridge_abi.RollupMetaData.GetAbi()
|
||||
if err != nil {
|
||||
log.Error("Failed to create rollup sender", "err", err)
|
||||
log.Error("Get RollupABI failed", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prv, err := crypto.HexToECDSA(cfg.PrivateKey)
|
||||
if err != nil {
|
||||
log.Error("Failed to import private key from config file")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// @todo use different sender for relayer, block commit and proof finalize
|
||||
sender, err := sender.NewSender(ctx, cfg.SenderConfig, prv)
|
||||
if err != nil {
|
||||
log.Error("Failed to create sender", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Layer2Relayer{
|
||||
ctx: ctx,
|
||||
client: ethClient,
|
||||
db: db,
|
||||
messageSender: messageSender,
|
||||
messageCh: messageSender.ConfirmChan(),
|
||||
l1MessengerABI: bridge_abi.L1MessengerMetaABI,
|
||||
rollupSender: rollupSender,
|
||||
rollupCh: rollupSender.ConfirmChan(),
|
||||
l1RollupABI: bridge_abi.RollupMetaABI,
|
||||
cfg: cfg,
|
||||
proofGenerationFreq: proofGenFreq,
|
||||
skippedOpcodes: skippedOpcodes,
|
||||
processingMessage: map[string]string{},
|
||||
processingCommitment: map[string]string{},
|
||||
processingFinalization: map[string]string{},
|
||||
stopCh: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
client: ethClient,
|
||||
sender: sender,
|
||||
db: db,
|
||||
l1MessengerABI: l1MessengerABI,
|
||||
l1RollupABI: l1RollupABI,
|
||||
cfg: cfg,
|
||||
proofGenerationFreq: proofGenFreq,
|
||||
skippedOpcodes: skippedOpcodes,
|
||||
processingMessage: map[string]string{},
|
||||
processingBlock: map[string]uint64{},
|
||||
processingProof: map[string]uint64{},
|
||||
stopCh: make(chan struct{}),
|
||||
confirmationCh: sender.ConfirmChan(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -100,38 +111,29 @@ func (r *Layer2Relayer) ProcessSavedEvents() {
|
||||
log.Error("Failed to fetch unprocessed L2 messages", "err", err)
|
||||
return
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
if err := r.processSavedEvent(msg); err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("failed to process l2 saved event", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) processSavedEvent(msg *orm.L2Message) error {
|
||||
msg := msgs[0]
|
||||
// @todo add support to relay multiple messages
|
||||
batch, err := r.db.GetLatestFinalizedBatch()
|
||||
latestFinalizeHeight, err := r.db.GetLatestFinalizedBlock()
|
||||
if err != nil {
|
||||
log.Error("GetLatestFinalizedBatch failed", "err", err)
|
||||
return err
|
||||
log.Error("GetLatestFinalizedBlock failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
if batch.EndBlockNumber < msg.Height {
|
||||
if latestFinalizeHeight < msg.Height {
|
||||
// log.Warn("corresponding block not finalized", "status", status)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// @todo fetch merkle proof from l2geth
|
||||
log.Info("Processing L2 Message", "msg.nonce", msg.Nonce, "msg.height", msg.Height)
|
||||
|
||||
proof := bridge_abi.IL1ScrollMessengerL2MessageProof{
|
||||
BlockHeight: big.NewInt(int64(msg.Height)),
|
||||
BatchIndex: big.NewInt(int64(batch.Index)),
|
||||
BlockNumber: big.NewInt(int64(msg.Height)),
|
||||
MerkleProof: make([]byte, 0),
|
||||
}
|
||||
from := common.HexToAddress(msg.Sender)
|
||||
sender := common.HexToAddress(msg.Sender)
|
||||
target := common.HexToAddress(msg.Target)
|
||||
value, ok := big.NewInt(0).SetString(msg.Value, 10)
|
||||
if !ok {
|
||||
@@ -143,19 +145,17 @@ func (r *Layer2Relayer) processSavedEvent(msg *orm.L2Message) error {
|
||||
deadline := big.NewInt(int64(msg.Deadline))
|
||||
msgNonce := big.NewInt(int64(msg.Nonce))
|
||||
calldata := common.Hex2Bytes(msg.Calldata)
|
||||
data, err := r.l1MessengerABI.Pack("relayMessageWithProof", from, target, value, fee, deadline, msgNonce, calldata, proof)
|
||||
data, err := r.l1MessengerABI.Pack("relayMessageWithProof", sender, target, value, fee, deadline, msgNonce, calldata, proof)
|
||||
if err != nil {
|
||||
log.Error("Failed to pack relayMessageWithProof", "msg.nonce", msg.Nonce, "err", err)
|
||||
// TODO: need to skip this message by changing its status to MsgError
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := r.messageSender.SendTransaction(msg.Layer2Hash, &r.cfg.MessengerContractAddress, big.NewInt(0), data)
|
||||
hash, err := r.sender.SendTransaction(msg.Layer2Hash, &r.cfg.MessengerContractAddress, big.NewInt(0), data)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("Failed to send relayMessageWithProof tx to layer1 ", "msg.height", msg.Height, "msg.Layer2Hash", msg.Layer2Hash, "err", err)
|
||||
}
|
||||
return err
|
||||
log.Error("Failed to send relayMessageWithProof tx to L1", "err", err)
|
||||
return
|
||||
}
|
||||
log.Info("relayMessageWithProof to layer1", "layer2hash", msg.Layer2Hash, "txhash", hash.String())
|
||||
|
||||
@@ -164,198 +164,203 @@ func (r *Layer2Relayer) processSavedEvent(msg *orm.L2Message) error {
|
||||
err = r.db.UpdateLayer2StatusAndLayer1Hash(r.ctx, msg.Layer2Hash, hash.String(), orm.MsgSubmitted)
|
||||
if err != nil {
|
||||
log.Error("UpdateLayer2StatusAndLayer1Hash failed", "layer2hash", msg.Layer2Hash, "err", err)
|
||||
return err
|
||||
}
|
||||
r.processingMessage[msg.Layer2Hash] = msg.Layer2Hash
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessPendingBatches submit batch data to layer 1 rollup contract
|
||||
func (r *Layer2Relayer) ProcessPendingBatches() {
|
||||
// batches are sorted by batch index in increasing order
|
||||
batchesInDB, err := r.db.GetPendingBatches()
|
||||
// ProcessPendingBlocks submit block data to layer rollup contract
|
||||
func (r *Layer2Relayer) ProcessPendingBlocks() {
|
||||
// blocks are sorted by height in increasing order
|
||||
blocksInDB, err := r.db.GetPendingBlocks()
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch pending L2 batches", "err", err)
|
||||
log.Error("Failed to fetch pending L2 blocks", "err", err)
|
||||
return
|
||||
}
|
||||
if len(batchesInDB) == 0 {
|
||||
if len(blocksInDB) == 0 {
|
||||
return
|
||||
}
|
||||
id := batchesInDB[0]
|
||||
// @todo add support to relay multiple batches
|
||||
height := blocksInDB[0]
|
||||
// @todo add support to relay multiple blocks
|
||||
|
||||
batches, err := r.db.GetBlockBatches(map[string]interface{}{"id": id})
|
||||
if err != nil || len(batches) == 0 {
|
||||
log.Error("Failed to GetBlockBatches", "batch_id", id, "err", err)
|
||||
return
|
||||
}
|
||||
batch := batches[0]
|
||||
|
||||
traces, err := r.db.GetBlockTraces(map[string]interface{}{"batch_id": id}, "ORDER BY number ASC")
|
||||
if err != nil || len(traces) == 0 {
|
||||
log.Error("Failed to GetBlockTraces", "batch_id", id, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
layer2Batch := &bridge_abi.IZKRollupLayer2Batch{
|
||||
BatchIndex: batch.Index,
|
||||
ParentHash: common.HexToHash(batch.ParentHash),
|
||||
Blocks: make([]bridge_abi.IZKRollupLayer2BlockHeader, len(traces)),
|
||||
}
|
||||
|
||||
parentHash := common.HexToHash(batch.ParentHash)
|
||||
for i, trace := range traces {
|
||||
layer2Batch.Blocks[i] = bridge_abi.IZKRollupLayer2BlockHeader{
|
||||
BlockHash: trace.Header.Hash(),
|
||||
ParentHash: parentHash,
|
||||
BaseFee: trace.Header.BaseFee,
|
||||
StateRoot: trace.StorageTrace.RootAfter,
|
||||
BlockHeight: trace.Header.Number.Uint64(),
|
||||
GasUsed: 0,
|
||||
Timestamp: trace.Header.Time,
|
||||
ExtraData: make([]byte, 0),
|
||||
Txs: make([]bridge_abi.IZKRollupLayer2Transaction, len(trace.Transactions)),
|
||||
}
|
||||
for j, tx := range trace.Transactions {
|
||||
layer2Batch.Blocks[i].Txs[j] = bridge_abi.IZKRollupLayer2Transaction{
|
||||
Caller: tx.From,
|
||||
Nonce: tx.Nonce,
|
||||
Gas: tx.Gas,
|
||||
GasPrice: tx.GasPrice.ToInt(),
|
||||
Value: tx.Value.ToInt(),
|
||||
Data: common.Hex2Bytes(tx.Data),
|
||||
R: tx.R.ToInt(),
|
||||
S: tx.S.ToInt(),
|
||||
V: tx.V.ToInt().Uint64(),
|
||||
}
|
||||
if tx.To != nil {
|
||||
layer2Batch.Blocks[i].Txs[j].Target = *tx.To
|
||||
}
|
||||
layer2Batch.Blocks[i].GasUsed += trace.ExecutionResults[j].Gas
|
||||
}
|
||||
|
||||
// for next iteration
|
||||
parentHash = layer2Batch.Blocks[i].BlockHash
|
||||
}
|
||||
|
||||
data, err := r.l1RollupABI.Pack("commitBatch", layer2Batch)
|
||||
// will fetch missing block result from l2geth
|
||||
trace, err := r.getOrFetchBlockResultByHeight(height)
|
||||
if err != nil {
|
||||
log.Error("Failed to pack commitBatch", "id", id, "index", batch.Index, "err", err)
|
||||
log.Error("getOrFetchBlockResultByHeight failed",
|
||||
"height", height,
|
||||
"err", err,
|
||||
)
|
||||
return
|
||||
}
|
||||
if trace == nil {
|
||||
return
|
||||
}
|
||||
parentHash, err := r.getOrFetchBlockHashByHeight(height - 1)
|
||||
if err != nil {
|
||||
log.Error("getOrFetchBlockHashByHeight for parent block failed",
|
||||
"parent height", height-1,
|
||||
"err", err,
|
||||
)
|
||||
return
|
||||
}
|
||||
if parentHash == nil {
|
||||
log.Error("parent hash is empty",
|
||||
"height", height,
|
||||
"err", err,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := r.rollupSender.SendTransaction(id, &r.cfg.RollupContractAddress, big.NewInt(0), data)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("Failed to send commitBatch tx to layer1 ", "id", id, "index", batch.Index, "err", err)
|
||||
header := bridge_abi.IZKRollupBlockHeader{
|
||||
BlockHash: trace.BlockTrace.Hash,
|
||||
ParentHash: *parentHash,
|
||||
BaseFee: trace.BlockTrace.BaseFee.ToInt(),
|
||||
StateRoot: trace.StorageTrace.RootAfter,
|
||||
BlockHeight: trace.BlockTrace.Number.ToInt().Uint64(),
|
||||
GasUsed: 0,
|
||||
Timestamp: trace.BlockTrace.Time,
|
||||
ExtraData: make([]byte, 0),
|
||||
}
|
||||
txns := make([]bridge_abi.IZKRollupLayer2Transaction, len(trace.BlockTrace.Transactions))
|
||||
for i, tx := range trace.BlockTrace.Transactions {
|
||||
txns[i] = bridge_abi.IZKRollupLayer2Transaction{
|
||||
Caller: tx.From,
|
||||
Nonce: tx.Nonce,
|
||||
Gas: tx.Gas,
|
||||
GasPrice: tx.GasPrice.ToInt(),
|
||||
Value: tx.Value.ToInt(),
|
||||
Data: common.Hex2Bytes(tx.Data),
|
||||
}
|
||||
if tx.To != nil {
|
||||
txns[i].Target = *tx.To
|
||||
}
|
||||
header.GasUsed += trace.ExecutionResults[i].Gas
|
||||
}
|
||||
|
||||
data, err := r.l1RollupABI.Pack("commitBlock", header, txns)
|
||||
if err != nil {
|
||||
log.Error("Failed to pack commitBlock", "height", height, "err", err)
|
||||
return
|
||||
}
|
||||
log.Info("commitBatch in layer1", "id", id, "index", batch.Index, "hash", hash)
|
||||
hash, err := r.sender.SendTransaction(strconv.FormatUint(height, 10), &r.cfg.RollupContractAddress, big.NewInt(0), data)
|
||||
if err != nil {
|
||||
log.Error("Failed to send commitBlock tx to layer1 ", "height", height, "err", err)
|
||||
return
|
||||
}
|
||||
log.Info("commitBlock in layer1", "height", height, "hash", hash)
|
||||
|
||||
// record and sync with db, @todo handle db error
|
||||
err = r.db.UpdateCommitTxHashAndRollupStatus(r.ctx, id, hash.String(), orm.RollupCommitting)
|
||||
err = r.db.UpdateRollupTxHashAndStatus(r.ctx, height, hash.String(), orm.RollupCommitting)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitTxHashAndRollupStatus failed", "id", id, "index", batch.Index, "err", err)
|
||||
log.Error("UpdateRollupTxHashAndStatus failed", "height", height, "err", err)
|
||||
}
|
||||
r.processingCommitment[id] = id
|
||||
r.processingBlock[strconv.FormatUint(height, 10)] = height
|
||||
}
|
||||
|
||||
// ProcessCommittedBatches submit proof to layer 1 rollup contract
|
||||
func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
// batches are sorted by batch index in increasing order
|
||||
batches, err := r.db.GetCommittedBatches()
|
||||
// ProcessCommittedBlocks submit proof to layer rollup contract
|
||||
func (r *Layer2Relayer) ProcessCommittedBlocks() {
|
||||
// blocks are sorted by height in increasing order
|
||||
blocksInDB, err := r.db.GetCommittedBlocks()
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch committed L2 batches", "err", err)
|
||||
log.Error("Failed to fetch committed L2 blocks", "err", err)
|
||||
return
|
||||
}
|
||||
if len(batches) == 0 {
|
||||
if len(blocksInDB) == 0 {
|
||||
return
|
||||
}
|
||||
id := batches[0]
|
||||
// @todo add support to relay multiple batches
|
||||
height := blocksInDB[0]
|
||||
// @todo add support to relay multiple blocks
|
||||
|
||||
status, err := r.db.GetProvingStatusByID(id)
|
||||
status, err := r.db.GetBlockStatusByNumber(height)
|
||||
if err != nil {
|
||||
log.Error("GetProvingStatusByID failed", "id", id, "err", err)
|
||||
log.Error("GetBlockStatusByNumber failed", "height", height, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
case orm.ProvingTaskUnassigned, orm.ProvingTaskAssigned:
|
||||
case orm.BlockUnassigned, orm.BlockAssigned:
|
||||
// The proof for this block is not ready yet.
|
||||
return
|
||||
|
||||
case orm.ProvingTaskProved:
|
||||
case orm.BlockProved:
|
||||
// It's an intermediate state. The roller manager received the proof but has not verified
|
||||
// the proof yet. We don't roll up the proof until it's verified.
|
||||
return
|
||||
|
||||
case orm.ProvingTaskFailed, orm.ProvingTaskSkipped:
|
||||
if err = r.db.UpdateRollupStatus(r.ctx, id, orm.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "id", id, "err", err)
|
||||
case orm.BlockFailed, orm.BlockSkipped:
|
||||
if err = r.db.UpdateRollupStatus(r.ctx, height, orm.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "height", height, "err", err)
|
||||
}
|
||||
|
||||
case orm.ProvingTaskVerified:
|
||||
log.Info("Start to roll up zk proof", "id", id)
|
||||
case orm.BlockVerified:
|
||||
log.Info("Start to roll up zk proof", "height", height)
|
||||
success := false
|
||||
|
||||
defer func() {
|
||||
// TODO: need to revisit this and have a more fine-grained error handling
|
||||
if !success {
|
||||
log.Info("Failed to upload the proof, change rollup status to FinalizationSkipped", "id", id)
|
||||
if err = r.db.UpdateRollupStatus(r.ctx, id, orm.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "id", id, "err", err)
|
||||
log.Info("Failed to upload the proof, change rollup status to FinalizationSkipped", "height", height)
|
||||
if err = r.db.UpdateRollupStatus(r.ctx, height, orm.RollupFinalizationSkipped); err != nil {
|
||||
log.Warn("UpdateRollupStatus failed", "height", height, "err", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
proofBuffer, instanceBuffer, err := r.db.GetVerifiedProofAndInstanceByID(id)
|
||||
proofBuffer, instanceBuffer, err := r.db.GetVerifiedProofAndInstanceByNumber(height)
|
||||
if err != nil {
|
||||
log.Warn("fetch get proof by id failed", "id", id, "err", err)
|
||||
log.Warn("fetch get proof by height failed", "height", height, "err", err)
|
||||
return
|
||||
}
|
||||
if proofBuffer == nil || instanceBuffer == nil {
|
||||
log.Warn("proof or instance not ready", "id", id)
|
||||
log.Warn("proof or instance not ready", "height", height)
|
||||
return
|
||||
}
|
||||
if len(proofBuffer)%32 != 0 {
|
||||
log.Error("proof buffer has wrong length", "id", id, "length", len(proofBuffer))
|
||||
log.Error("proof buffer has wrong length", "height", height, "length", len(proofBuffer))
|
||||
return
|
||||
}
|
||||
if len(instanceBuffer)%32 != 0 {
|
||||
log.Warn("instance buffer has wrong length", "id", id, "length", len(instanceBuffer))
|
||||
log.Warn("instance buffer has wrong length", "height", height, "length", len(instanceBuffer))
|
||||
return
|
||||
}
|
||||
|
||||
proof := bufferToUint256Le(proofBuffer)
|
||||
instance := bufferToUint256Le(instanceBuffer)
|
||||
data, err := r.l1RollupABI.Pack("finalizeBatchWithProof", common.HexToHash(id), proof, instance)
|
||||
if err != nil {
|
||||
log.Error("Pack finalizeBatchWithProof failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
txHash, err := r.rollupSender.SendTransaction(id, &r.cfg.RollupContractAddress, big.NewInt(0), data)
|
||||
hash := &txHash
|
||||
// it must in db
|
||||
hash, err := r.db.GetHashByNumber(height)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("finalizeBatchWithProof in layer1 failed", "id", id, "err", err)
|
||||
}
|
||||
log.Warn("fetch missing block result by height failed", "height", height, "err", err)
|
||||
}
|
||||
if hash == nil {
|
||||
// only happen when trace validate failed
|
||||
return
|
||||
}
|
||||
log.Info("finalizeBatchWithProof in layer1", "id", id, "hash", hash)
|
||||
data, err := r.l1RollupABI.Pack("finalizeBlockWithProof", hash, proof, instance)
|
||||
if err != nil {
|
||||
log.Error("Pack finalizeBlockWithProof failed", err)
|
||||
return
|
||||
}
|
||||
txHash, err := r.sender.SendTransaction(strconv.FormatUint(height, 10), &r.cfg.RollupContractAddress, big.NewInt(0), data)
|
||||
hash = &txHash
|
||||
if err != nil {
|
||||
log.Error("finalizeBlockWithProof in layer1 failed",
|
||||
"height", height,
|
||||
"err", err,
|
||||
)
|
||||
return
|
||||
}
|
||||
log.Info("finalizeBlockWithProof in layer1", "height", height, "hash", hash)
|
||||
|
||||
// record and sync with db, @todo handle db error
|
||||
err = r.db.UpdateFinalizeTxHashAndRollupStatus(r.ctx, id, hash.String(), orm.RollupFinalizing)
|
||||
err = r.db.UpdateFinalizeTxHashAndStatus(r.ctx, height, hash.String(), orm.RollupFinalizing)
|
||||
if err != nil {
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed", "id", id, "err", err)
|
||||
log.Warn("UpdateFinalizeTxHashAndStatus failed", "height", height, "err", err)
|
||||
}
|
||||
success = true
|
||||
r.processingFinalization[id] = id
|
||||
r.processingProof[strconv.FormatUint(height, 10)] = height
|
||||
|
||||
default:
|
||||
log.Error("encounter unreachable case in ProcessCommittedBatches",
|
||||
log.Error("encounter unreachable case in ProcessCommittedBlocks",
|
||||
"block_status", status,
|
||||
)
|
||||
}
|
||||
@@ -372,11 +377,9 @@ func (r *Layer2Relayer) Start() {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
r.ProcessSavedEvents()
|
||||
r.ProcessPendingBatches()
|
||||
r.ProcessCommittedBatches()
|
||||
case confirmation := <-r.messageCh:
|
||||
r.handleConfirmation(confirmation)
|
||||
case confirmation := <-r.rollupCh:
|
||||
r.ProcessPendingBlocks()
|
||||
r.ProcessCommittedBlocks()
|
||||
case confirmation := <-r.confirmationCh:
|
||||
r.handleConfirmation(confirmation)
|
||||
case <-r.stopCh:
|
||||
return
|
||||
@@ -390,6 +393,64 @@ func (r *Layer2Relayer) Stop() {
|
||||
close(r.stopCh)
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) getOrFetchBlockHashByHeight(height uint64) (*common.Hash, error) {
|
||||
hash, err := r.db.GetHashByNumber(height)
|
||||
if err != nil {
|
||||
block, err := r.client.BlockByNumber(r.ctx, big.NewInt(int64(height)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := block.Hash()
|
||||
return &x, err
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) getOrFetchBlockResultByHeight(height uint64) (*types.BlockResult, error) {
|
||||
tracesInDB, err := r.db.GetBlockResults(map[string]interface{}{"number": height})
|
||||
if err != nil {
|
||||
log.Warn("GetBlockResults failed", "height", height, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(tracesInDB) == 0 {
|
||||
return r.fetchMissingBlockResultByHeight(height)
|
||||
}
|
||||
return tracesInDB[0], nil
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) fetchMissingBlockResultByHeight(height uint64) (*types.BlockResult, error) {
|
||||
header, err := r.client.HeaderByNumber(r.ctx, big.NewInt(int64(height)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trace, err := r.client.GetBlockResultByHash(r.ctx, header.Hash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blockTraceIsValid(trace) {
|
||||
// skip verify for unsupported block
|
||||
skip := false
|
||||
if height%r.proofGenerationFreq != 0 {
|
||||
log.Info("skip proof generation", "block", height)
|
||||
skip = true
|
||||
} else if TraceHasUnsupportedOpcodes(r.skippedOpcodes, trace) {
|
||||
log.Info("block has unsupported opcodes, skip proof generation", "block", height)
|
||||
skip = true
|
||||
}
|
||||
if skip {
|
||||
if err = r.db.InsertBlockResultsWithStatus(r.ctx, []*types.BlockResult{trace}, orm.BlockSkipped); err != nil {
|
||||
log.Error("failed to store missing blockResult", "err", err)
|
||||
}
|
||||
} else {
|
||||
if err = r.db.InsertBlockResultsWithStatus(r.ctx, []*types.BlockResult{trace}, orm.BlockUnassigned); err != nil {
|
||||
log.Error("failed to store missing blockResult", "err", err)
|
||||
}
|
||||
}
|
||||
return trace, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
|
||||
transactionType := "Unknown"
|
||||
// check whether it is message relay transaction
|
||||
@@ -398,36 +459,36 @@ func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
|
||||
// @todo handle db error
|
||||
err := r.db.UpdateLayer2StatusAndLayer1Hash(r.ctx, layer2Hash, confirmation.TxHash.String(), orm.MsgConfirmed)
|
||||
if err != nil {
|
||||
log.Warn("UpdateLayer2StatusAndLayer1Hash failed", "layer2Hash", layer2Hash, "err", err)
|
||||
log.Warn("UpdateLayer2StatusAndLayer1Hash failed", "err", err)
|
||||
}
|
||||
delete(r.processingMessage, confirmation.ID)
|
||||
}
|
||||
|
||||
// check whether it is block commitment transaction
|
||||
if batch_id, ok := r.processingCommitment[confirmation.ID]; ok {
|
||||
if blockHeight, ok := r.processingBlock[confirmation.ID]; ok {
|
||||
transactionType = "BlockCommitment"
|
||||
// @todo handle db error
|
||||
err := r.db.UpdateCommitTxHashAndRollupStatus(r.ctx, batch_id, confirmation.TxHash.String(), orm.RollupCommitted)
|
||||
err := r.db.UpdateRollupTxHashAndStatus(r.ctx, blockHeight, confirmation.TxHash.String(), orm.RollupCommitted)
|
||||
if err != nil {
|
||||
log.Warn("UpdateCommitTxHashAndRollupStatus failed", "batch_id", batch_id, "err", err)
|
||||
log.Warn("UpdateRollupTxHashAndStatus failed", "err", err)
|
||||
}
|
||||
delete(r.processingCommitment, confirmation.ID)
|
||||
delete(r.processingBlock, confirmation.ID)
|
||||
}
|
||||
|
||||
// check whether it is proof finalization transaction
|
||||
if batch_id, ok := r.processingFinalization[confirmation.ID]; ok {
|
||||
if blockHeight, ok := r.processingProof[confirmation.ID]; ok {
|
||||
transactionType = "ProofFinalization"
|
||||
// @todo handle db error
|
||||
err := r.db.UpdateFinalizeTxHashAndRollupStatus(r.ctx, batch_id, confirmation.TxHash.String(), orm.RollupFinalized)
|
||||
err := r.db.UpdateFinalizeTxHashAndStatus(r.ctx, blockHeight, confirmation.TxHash.String(), orm.RollupFinalized)
|
||||
if err != nil {
|
||||
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed", "batch_id", batch_id, "err", err)
|
||||
log.Warn("UpdateFinalizeTxHashAndStatus failed", "err", err)
|
||||
}
|
||||
delete(r.processingFinalization, confirmation.ID)
|
||||
delete(r.processingProof, confirmation.ID)
|
||||
|
||||
// try to delete block trace
|
||||
err = r.db.DeleteTracesByBatchID(batch_id)
|
||||
err = r.db.DeleteTraceByNumber(blockHeight)
|
||||
if err != nil {
|
||||
log.Warn("DeleteTracesByBatchID failed", "batch_id", batch_id, "err", err)
|
||||
log.Warn("DeleteTraceByNumber failed", "err", err)
|
||||
}
|
||||
}
|
||||
log.Info("transaction confirmed in layer1", "type", transactionType, "confirmation", confirmation)
|
||||
|
||||
@@ -3,23 +3,27 @@ package l2_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/bridge/l2"
|
||||
"scroll-tech/common/docker"
|
||||
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/migrate"
|
||||
"scroll-tech/database/orm"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
|
||||
"scroll-tech/bridge/l2"
|
||||
"scroll-tech/bridge/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
templateL2Message = []*orm.L2Message{
|
||||
templateLayer2Message = []*orm.Layer2Message{
|
||||
{
|
||||
Nonce: 1,
|
||||
Height: 1,
|
||||
@@ -33,165 +37,198 @@ var (
|
||||
Layer2Hash: "hash0",
|
||||
},
|
||||
}
|
||||
l1Docker docker.ImgInstance
|
||||
l2Docker docker.ImgInstance
|
||||
dbDocker docker.ImgInstance
|
||||
)
|
||||
|
||||
func testCreateNewRelayer(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), l2Cli, cfg.L2Config.ProofGenerationFreq, cfg.L2Config.SkippedOpcodes, int64(cfg.L2Config.Confirmations), db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
defer relayer.Stop()
|
||||
|
||||
relayer.Start()
|
||||
func setupEnv(t *testing.T) {
|
||||
l1Docker = mock.NewTestL1Docker(t, TEST_CONFIG)
|
||||
l2Docker = mock.NewTestL2Docker(t, TEST_CONFIG)
|
||||
dbDocker = mock.GetDbDocker(t, TEST_CONFIG)
|
||||
}
|
||||
|
||||
func testL2RelayerProcessSaveEvents(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
func TestRelayerFunction(t *testing.T) {
|
||||
// Setup
|
||||
setupEnv(t)
|
||||
|
||||
l2Cfg := cfg.L2Config
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), l2Cli, l2Cfg.ProofGenerationFreq, l2Cfg.SkippedOpcodes, int64(l2Cfg.Confirmations), db, l2Cfg.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
defer relayer.Stop()
|
||||
t.Run("TestCreateNewRelayer", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1Docker.Endpoint()
|
||||
|
||||
err = db.SaveL2Messages(context.Background(), templateL2Message)
|
||||
assert.NoError(t, err)
|
||||
client, err := ethclient.Dial(l2Docker.Endpoint())
|
||||
assert.NoError(t, err)
|
||||
|
||||
traces := []*types.BlockTrace{
|
||||
{
|
||||
Header: &types.Header{
|
||||
Number: big.NewInt(int64(templateL2Message[0].Height)),
|
||||
db, err := database.NewOrmFactory(TEST_CONFIG.DB_CONFIG)
|
||||
assert.NoError(t, err)
|
||||
|
||||
skippedOpcodes := make(map[string]struct{}, len(cfg.L2Config.SkippedOpcodes))
|
||||
for _, op := range cfg.L2Config.SkippedOpcodes {
|
||||
skippedOpcodes[op] = struct{}{}
|
||||
}
|
||||
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), client, cfg.L2Config.ProofGenerationFreq, skippedOpcodes, int64(cfg.L2Config.Confirmations), db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
relayer.Start()
|
||||
|
||||
defer relayer.Stop()
|
||||
})
|
||||
|
||||
t.Run("TestL2RelayerProcessSaveEvents", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1Docker.Endpoint()
|
||||
|
||||
client, err := ethclient.Dial(l2Docker.Endpoint())
|
||||
assert.NoError(t, err)
|
||||
|
||||
mock.ClearDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
db := mock.PrepareDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
|
||||
skippedOpcodes := make(map[string]struct{}, len(cfg.L2Config.SkippedOpcodes))
|
||||
for _, op := range cfg.L2Config.SkippedOpcodes {
|
||||
skippedOpcodes[op] = struct{}{}
|
||||
}
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), client, cfg.L2Config.ProofGenerationFreq, skippedOpcodes, int64(cfg.L2Config.Confirmations), db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = db.SaveLayer2Messages(context.Background(), templateLayer2Message)
|
||||
assert.NoError(t, err)
|
||||
blocks := []*orm.RollupResult{
|
||||
{
|
||||
Number: 3,
|
||||
Status: orm.RollupFinalized,
|
||||
RollupTxHash: "Rollup Test Hash",
|
||||
FinalizeTxHash: "Finalized Hash",
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: &types.Header{
|
||||
Number: big.NewInt(int64(templateL2Message[0].Height + 1)),
|
||||
}
|
||||
err = db.InsertPendingBlocks(context.Background(), []uint64{uint64(blocks[0].Number)})
|
||||
assert.NoError(t, err)
|
||||
err = db.UpdateRollupStatus(context.Background(), uint64(blocks[0].Number), orm.RollupFinalized)
|
||||
assert.NoError(t, err)
|
||||
relayer.ProcessSavedEvents()
|
||||
|
||||
msg, err := db.GetLayer2MessageByNonce(templateLayer2Message[0].Nonce)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orm.MsgSubmitted, msg.Status)
|
||||
|
||||
})
|
||||
|
||||
t.Run("TestL2RelayerProcessPendingBlocks", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1Docker.Endpoint()
|
||||
|
||||
client, err := ethclient.Dial(l2Docker.Endpoint())
|
||||
assert.NoError(t, err)
|
||||
|
||||
mock.ClearDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
db := mock.PrepareDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
|
||||
skippedOpcodes := make(map[string]struct{}, len(cfg.L2Config.SkippedOpcodes))
|
||||
for _, op := range cfg.L2Config.SkippedOpcodes {
|
||||
skippedOpcodes[op] = struct{}{}
|
||||
}
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), client, cfg.L2Config.ProofGenerationFreq, skippedOpcodes, int64(cfg.L2Config.Confirmations), db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// this blockresult has number of 0x4, need to change it to match the testcase
|
||||
// In this testcase scenario, db will store two blocks with height 0x4 and 0x3
|
||||
var results []*types.BlockResult
|
||||
|
||||
templateBlockResult, err := os.ReadFile("../../common/testdata/blockResult_relayer_parent.json")
|
||||
assert.NoError(t, err)
|
||||
blockResult := &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
results = append(results, blockResult)
|
||||
templateBlockResult, err = os.ReadFile("../../common/testdata/blockResult_relayer.json")
|
||||
assert.NoError(t, err)
|
||||
blockResult = &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
results = append(results, blockResult)
|
||||
|
||||
err = db.InsertBlockResultsWithStatus(context.Background(), results, orm.BlockUnassigned)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blocks := []*orm.RollupResult{
|
||||
{
|
||||
Number: 4,
|
||||
Status: 1,
|
||||
RollupTxHash: "Rollup Test Hash",
|
||||
FinalizeTxHash: "Finalized Hash",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = db.InsertBlockTraces(context.Background(), traces)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
err = db.InsertPendingBlocks(context.Background(), []uint64{uint64(blocks[0].Number)})
|
||||
assert.NoError(t, err)
|
||||
err = db.UpdateRollupStatus(context.Background(), uint64(blocks[0].Number), orm.RollupPending)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbTx, err := db.Beginx()
|
||||
assert.NoError(t, err)
|
||||
batchID, err := db.NewBatchInDBTx(dbTx,
|
||||
&orm.BlockInfo{Number: templateL2Message[0].Height},
|
||||
&orm.BlockInfo{Number: templateL2Message[0].Height + 1},
|
||||
"0f", 1, 194676) // parentHash & totalTxNum & totalL2Gas don't really matter here
|
||||
assert.NoError(t, err)
|
||||
err = db.SetBatchIDForBlocksInDBTx(dbTx, []uint64{
|
||||
templateL2Message[0].Height,
|
||||
templateL2Message[0].Height + 1}, batchID)
|
||||
assert.NoError(t, err)
|
||||
err = dbTx.Commit()
|
||||
assert.NoError(t, err)
|
||||
relayer.ProcessPendingBlocks()
|
||||
|
||||
err = db.UpdateRollupStatus(context.Background(), batchID, orm.RollupFinalized)
|
||||
assert.NoError(t, err)
|
||||
// Check if Rollup Result is changed successfully
|
||||
status, err := db.GetRollupStatus(uint64(blocks[0].Number))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orm.RollupCommitting, status)
|
||||
})
|
||||
|
||||
relayer.ProcessSavedEvents()
|
||||
t.Run("TestL2RelayerProcessCommittedBlocks", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1Docker.Endpoint()
|
||||
|
||||
msg, err := db.GetL2MessageByNonce(templateL2Message[0].Nonce)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orm.MsgSubmitted, msg.Status)
|
||||
}
|
||||
|
||||
func testL2RelayerProcessPendingBatches(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
|
||||
l2Cfg := cfg.L2Config
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), l2Cli, l2Cfg.ProofGenerationFreq, l2Cfg.SkippedOpcodes, int64(l2Cfg.Confirmations), db, l2Cfg.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
defer relayer.Stop()
|
||||
|
||||
// this blockresult has number of 0x4, need to change it to match the testcase
|
||||
// In this testcase scenario, db will store two blocks with height 0x4 and 0x3
|
||||
var traces []*types.BlockTrace
|
||||
|
||||
templateBlockTrace, err := os.ReadFile("../../common/testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
blockTrace := &types.BlockTrace{}
|
||||
err = json.Unmarshal(templateBlockTrace, blockTrace)
|
||||
assert.NoError(t, err)
|
||||
traces = append(traces, blockTrace)
|
||||
templateBlockTrace, err = os.ReadFile("../../common/testdata/blockTrace_03.json")
|
||||
assert.NoError(t, err)
|
||||
blockTrace = &types.BlockTrace{}
|
||||
err = json.Unmarshal(templateBlockTrace, blockTrace)
|
||||
assert.NoError(t, err)
|
||||
traces = append(traces, blockTrace)
|
||||
|
||||
err = db.InsertBlockTraces(context.Background(), traces)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbTx, err := db.Beginx()
|
||||
assert.NoError(t, err)
|
||||
batchID, err := db.NewBatchInDBTx(dbTx,
|
||||
&orm.BlockInfo{Number: traces[0].Header.Number.Uint64()},
|
||||
&orm.BlockInfo{Number: traces[1].Header.Number.Uint64()},
|
||||
"ff", 1, 194676) // parentHash & totalTxNum & totalL2Gas don't really matter here
|
||||
assert.NoError(t, err)
|
||||
err = db.SetBatchIDForBlocksInDBTx(dbTx, []uint64{
|
||||
traces[0].Header.Number.Uint64(),
|
||||
traces[1].Header.Number.Uint64()}, batchID)
|
||||
assert.NoError(t, err)
|
||||
err = dbTx.Commit()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// err = db.UpdateRollupStatus(context.Background(), batchID, orm.RollupPending)
|
||||
// assert.NoError(t, err)
|
||||
|
||||
relayer.ProcessPendingBatches()
|
||||
|
||||
// Check if Rollup Result is changed successfully
|
||||
status, err := db.GetRollupStatus(batchID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orm.RollupCommitting, status)
|
||||
}
|
||||
|
||||
func testL2RelayerProcessCommittedBatches(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
|
||||
l2Cfg := cfg.L2Config
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), l2Cli, l2Cfg.ProofGenerationFreq, l2Cfg.SkippedOpcodes, int64(l2Cfg.Confirmations), db, l2Cfg.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
defer relayer.Stop()
|
||||
|
||||
dbTx, err := db.Beginx()
|
||||
assert.NoError(t, err)
|
||||
batchID, err := db.NewBatchInDBTx(dbTx, &orm.BlockInfo{}, &orm.BlockInfo{}, "0", 1, 194676) // startBlock & endBlock & parentHash & totalTxNum & totalL2Gas don't really matter here
|
||||
assert.NoError(t, err)
|
||||
err = dbTx.Commit()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = db.UpdateRollupStatus(context.Background(), batchID, orm.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
tInstanceCommitments := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
err = db.UpdateProofByID(context.Background(), batchID, tProof, tInstanceCommitments, 100)
|
||||
assert.NoError(t, err)
|
||||
err = db.UpdateProvingStatus(batchID, orm.ProvingTaskVerified)
|
||||
assert.NoError(t, err)
|
||||
|
||||
relayer.ProcessCommittedBatches()
|
||||
|
||||
status, err := db.GetRollupStatus(batchID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orm.RollupFinalizing, status)
|
||||
client, err := ethclient.Dial(l2Docker.Endpoint())
|
||||
assert.NoError(t, err)
|
||||
|
||||
mock.ClearDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
db := mock.PrepareDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
|
||||
skippedOpcodes := make(map[string]struct{}, len(cfg.L2Config.SkippedOpcodes))
|
||||
for _, op := range cfg.L2Config.SkippedOpcodes {
|
||||
skippedOpcodes[op] = struct{}{}
|
||||
}
|
||||
relayer, err := l2.NewLayer2Relayer(context.Background(), client, cfg.L2Config.ProofGenerationFreq, skippedOpcodes, int64(cfg.L2Config.Confirmations), db, cfg.L2Config.RelayerConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
templateBlockResult, err := os.ReadFile("../../common/testdata/blockResult_relayer.json")
|
||||
assert.NoError(t, err)
|
||||
blockResult := &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
err = db.InsertBlockResultsWithStatus(context.Background(), []*types.BlockResult{blockResult}, orm.BlockVerified)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blocks := []*orm.RollupResult{
|
||||
{
|
||||
Number: 4,
|
||||
Status: 1,
|
||||
RollupTxHash: "Rollup Test Hash",
|
||||
FinalizeTxHash: "Finalized Hash",
|
||||
},
|
||||
}
|
||||
err = db.InsertPendingBlocks(context.Background(), []uint64{uint64(blocks[0].Number)})
|
||||
assert.NoError(t, err)
|
||||
err = db.UpdateRollupStatus(context.Background(), uint64(blocks[0].Number), orm.RollupCommitted)
|
||||
assert.NoError(t, err)
|
||||
tProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
tStateProof := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
|
||||
|
||||
err = db.UpdateProofByNumber(context.Background(), uint64(blocks[0].Number), tProof, tStateProof, 100)
|
||||
assert.NoError(t, err)
|
||||
relayer.ProcessCommittedBlocks()
|
||||
|
||||
status, err := db.GetRollupStatus(uint64(blocks[0].Number))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orm.RollupFinalizing, status)
|
||||
})
|
||||
|
||||
// Teardown
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, l1Docker.Stop())
|
||||
assert.NoError(t, l2Docker.Stop())
|
||||
assert.NoError(t, dbDocker.Stop())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum"
|
||||
@@ -15,13 +14,17 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/event"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
|
||||
bridge_abi "scroll-tech/bridge/abi"
|
||||
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/orm"
|
||||
)
|
||||
|
||||
const (
|
||||
// BufferSize for the BlockResult channel
|
||||
BufferSize = 16
|
||||
|
||||
// BlockResultCacheSize for the latest handled blockresults in memory.
|
||||
BlockResultCacheSize = 64
|
||||
|
||||
// keccak256("SentMessage(address,address,uint256,uint256,uint256,bytes,uint256,uint256)")
|
||||
sentMessageEventSignature = "806b28931bc6fbe6c146babfb83d5c2b47e971edb43b4566f010577a0ee7d9f4"
|
||||
)
|
||||
@@ -46,13 +49,10 @@ type WatcherClient struct {
|
||||
|
||||
stopped uint64
|
||||
stopCh chan struct{}
|
||||
|
||||
// mutex for batch proposer
|
||||
bpMutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewL2WatcherClient take a l2geth instance to generate a l2watcherclient instance
|
||||
func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmations uint64, proofGenFreq uint64, skippedOpcodes map[string]struct{}, messengerAddress common.Address, orm database.OrmFactory) *WatcherClient {
|
||||
func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmations uint64, proofGenFreq uint64, skippedOpcodes map[string]struct{}, messengerAddress common.Address, messengerABI *abi.ABI, orm database.OrmFactory) *WatcherClient {
|
||||
savedHeight, err := orm.GetLayer2LatestWatchedHeight()
|
||||
if err != nil {
|
||||
log.Warn("fetch height from db failed", "err", err)
|
||||
@@ -68,10 +68,9 @@ func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmat
|
||||
proofGenerationFreq: proofGenFreq,
|
||||
skippedOpcodes: skippedOpcodes,
|
||||
messengerAddress: messengerAddress,
|
||||
messengerABI: bridge_abi.L2MessengerMetaABI,
|
||||
messengerABI: messengerABI,
|
||||
stopCh: make(chan struct{}),
|
||||
stopped: 0,
|
||||
bpMutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +82,7 @@ func (w *WatcherClient) Start() {
|
||||
}
|
||||
|
||||
// trigger by timer
|
||||
// TODO: make it configurable
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
@@ -93,20 +91,17 @@ func (w *WatcherClient) Start() {
|
||||
// get current height
|
||||
number, err := w.BlockNumber(w.ctx)
|
||||
if err != nil {
|
||||
log.Error("failed to get_BlockNumber", "err", err)
|
||||
log.Error("Failed to get_BlockNumber", "err", err)
|
||||
continue
|
||||
}
|
||||
if err := w.tryFetchRunningMissingBlocks(w.ctx, number); err != nil {
|
||||
log.Error("failed to fetchRunningMissingBlocks", "err", err)
|
||||
if err = w.tryFetchRunningMissingBlocks(w.ctx, number); err != nil {
|
||||
log.Error("Failed to fetchRunningMissingBlocks", "err", err)
|
||||
}
|
||||
|
||||
// @todo handle error
|
||||
if err := w.fetchContractEvent(number); err != nil {
|
||||
log.Error("failed to fetchContractEvent", "err", err)
|
||||
}
|
||||
|
||||
if err := w.tryProposeBatch(); err != nil {
|
||||
log.Error("failed to tryProposeBatch", "err", err)
|
||||
err = w.fetchContractEvent(number)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetchContractEvent", "err", err)
|
||||
}
|
||||
|
||||
case <-w.stopCh:
|
||||
@@ -121,51 +116,68 @@ func (w *WatcherClient) Stop() {
|
||||
w.stopCh <- struct{}{}
|
||||
}
|
||||
|
||||
const blockTracesFetchLimit = uint64(10)
|
||||
|
||||
// try fetch missing blocks if inconsistent
|
||||
func (w *WatcherClient) tryFetchRunningMissingBlocks(ctx context.Context, backTrackFrom uint64) error {
|
||||
// Get newest block in DB. must have blocks at that time.
|
||||
// Don't use "block_trace" table "trace" column's BlockTrace.Number,
|
||||
// Don't use "block_result" table "content" column's BlockTrace.Number,
|
||||
// because it might be empty if the corresponding rollup_result is finalized/finalization_skipped
|
||||
heightInDB, err := w.orm.GetBlockTracesLatestHeight()
|
||||
heightInDB, err := w.orm.GetBlockResultsLatestHeight()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to GetBlockTraces in DB: %v", err)
|
||||
return fmt.Errorf("Failed to GetBlockResults in DB: %v", err)
|
||||
}
|
||||
backTrackTo := uint64(0)
|
||||
if heightInDB > 0 {
|
||||
backTrackTo = uint64(heightInDB)
|
||||
}
|
||||
|
||||
// note that backTrackFrom >= backTrackTo because we are doing backtracking
|
||||
if backTrackFrom > backTrackTo+blockTracesFetchLimit {
|
||||
backTrackFrom = backTrackTo + blockTracesFetchLimit
|
||||
}
|
||||
|
||||
// start backtracking
|
||||
heights := []uint64{}
|
||||
toSkipped := []*types.BlockResult{}
|
||||
toUnassigned := []*types.BlockResult{}
|
||||
|
||||
var traces []*types.BlockTrace
|
||||
var (
|
||||
header *types.Header
|
||||
trace *types.BlockResult
|
||||
)
|
||||
for number := backTrackFrom; number > backTrackTo; number-- {
|
||||
log.Debug("retrieving block trace", "height", number)
|
||||
trace, err2 := w.GetBlockTraceByNumber(ctx, big.NewInt(int64(number)))
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("failed to GetBlockResultByHash: %v. number: %v", err2, number)
|
||||
header, err = w.HeaderByNumber(ctx, big.NewInt(int64(number)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get HeaderByNumber: %v. number: %v", err, number)
|
||||
}
|
||||
log.Info("retrieved block trace", "height", trace.Header.Number, "hash", trace.Header.Hash)
|
||||
|
||||
traces = append(traces, trace)
|
||||
trace, err = w.GetBlockResultByHash(ctx, header.Hash())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to GetBlockResultByHash: %v. number: %v", err, number)
|
||||
}
|
||||
log.Info("Retrieved block result", "height", header.Number, "hash", header.Hash())
|
||||
|
||||
heights = append(heights, number)
|
||||
if number%w.proofGenerationFreq != 0 {
|
||||
toSkipped = append(toSkipped, trace)
|
||||
} else if TraceHasUnsupportedOpcodes(w.skippedOpcodes, trace) {
|
||||
toSkipped = append(toSkipped, trace)
|
||||
} else {
|
||||
toUnassigned = append(toUnassigned, trace)
|
||||
}
|
||||
}
|
||||
if len(traces) > 0 {
|
||||
if err = w.orm.InsertBlockTraces(ctx, traces); err != nil {
|
||||
return fmt.Errorf("failed to batch insert BlockTraces: %v", err)
|
||||
|
||||
if len(heights) > 0 {
|
||||
if err = w.orm.InsertPendingBlocks(ctx, heights); err != nil {
|
||||
return fmt.Errorf("Failed to batch insert PendingBlocks: %v", err)
|
||||
}
|
||||
}
|
||||
if len(toSkipped) > 0 {
|
||||
if err = w.orm.InsertBlockResultsWithStatus(ctx, toSkipped, orm.BlockSkipped); err != nil {
|
||||
return fmt.Errorf("failed to batch insert PendingBlocks: %v", err)
|
||||
}
|
||||
}
|
||||
if len(toUnassigned) > 0 {
|
||||
if err = w.orm.InsertBlockResultsWithStatus(ctx, toUnassigned, orm.BlockUnassigned); err != nil {
|
||||
return fmt.Errorf("failed to batch insert PendingBlocks: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const contractEventsBlocksFetchLimit = int64(10)
|
||||
|
||||
// FetchContractEvent pull latest event logs from given contract address and save in DB
|
||||
func (w *WatcherClient) fetchContractEvent(blockHeight uint64) error {
|
||||
fromBlock := int64(w.processedMsgHeight) + 1
|
||||
@@ -175,10 +187,6 @@ func (w *WatcherClient) fetchContractEvent(blockHeight uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if toBlock > fromBlock+contractEventsBlocksFetchLimit {
|
||||
toBlock = fromBlock + contractEventsBlocksFetchLimit - 1
|
||||
}
|
||||
|
||||
// warning: uint int conversion...
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(fromBlock), // inclusive
|
||||
@@ -193,33 +201,33 @@ func (w *WatcherClient) fetchContractEvent(blockHeight uint64) error {
|
||||
|
||||
logs, err := w.FilterLogs(w.ctx, query)
|
||||
if err != nil {
|
||||
log.Error("failed to get event logs", "err", err)
|
||||
log.Error("Failed to get event logs", "err", err)
|
||||
return err
|
||||
}
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
log.Info("received new L2 messages", "fromBlock", fromBlock, "toBlock", toBlock,
|
||||
log.Info("Received new L2 messages", "fromBlock", fromBlock, "toBlock", toBlock,
|
||||
"cnt", len(logs))
|
||||
|
||||
eventLogs, err := parseBridgeEventLogs(logs, w.messengerABI)
|
||||
if err != nil {
|
||||
log.Error("failed to parse emitted event log", "err", err)
|
||||
log.Error("Failed to parse emitted event log", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.orm.SaveL2Messages(w.ctx, eventLogs)
|
||||
err = w.orm.SaveLayer2Messages(w.ctx, eventLogs)
|
||||
if err == nil {
|
||||
w.processedMsgHeight = uint64(toBlock)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseBridgeEventLogs(logs []types.Log, messengerABI *abi.ABI) ([]*orm.L2Message, error) {
|
||||
func parseBridgeEventLogs(logs []types.Log, messengerABI *abi.ABI) ([]*orm.Layer2Message, error) {
|
||||
// Need use contract abi to parse event Log
|
||||
// Can only be tested after we have our contracts set up
|
||||
|
||||
var parsedlogs []*orm.L2Message
|
||||
var parsedlogs []*orm.Layer2Message
|
||||
for _, vLog := range logs {
|
||||
event := struct {
|
||||
Target common.Address
|
||||
@@ -234,12 +242,12 @@ func parseBridgeEventLogs(logs []types.Log, messengerABI *abi.ABI) ([]*orm.L2Mes
|
||||
|
||||
err := messengerABI.UnpackIntoInterface(&event, "SentMessage", vLog.Data)
|
||||
if err != nil {
|
||||
log.Error("failed to unpack layer2 SentMessage event", "err", err)
|
||||
log.Error("Failed to unpack layer2 SentMessage event", "err", err)
|
||||
return parsedlogs, err
|
||||
}
|
||||
// target is in topics[1]
|
||||
event.Target = common.HexToAddress(vLog.Topics[1].String())
|
||||
parsedlogs = append(parsedlogs, &orm.L2Message{
|
||||
parsedlogs = append(parsedlogs, &orm.Layer2Message{
|
||||
Nonce: event.MessageNonce.Uint64(),
|
||||
Height: vLog.BlockNumber,
|
||||
Sender: event.Sender.String(),
|
||||
|
||||
@@ -24,7 +24,7 @@ func (r *WatcherClient) ReplayBlockResultByHash(blockNrOrHash rpc.BlockNumberOrH
|
||||
if len(params) == 0 {
|
||||
return false, errors.New("empty params")
|
||||
}
|
||||
trace, err := orm.GetBlockTraces(params)
|
||||
trace, err := orm.GetBlockResults(params)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -6,207 +6,314 @@ import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi/bind"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
bridge_abi "scroll-tech/bridge/abi"
|
||||
"scroll-tech/bridge/l2"
|
||||
"scroll-tech/bridge/mock_bridge"
|
||||
"scroll-tech/bridge/sender"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
"scroll-tech/common/utils"
|
||||
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/migrate"
|
||||
|
||||
db_config "scroll-tech/database"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
"scroll-tech/bridge/mock"
|
||||
"scroll-tech/bridge/mock_bridge"
|
||||
)
|
||||
|
||||
func testCreateNewWatcherAndStop(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
l2db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
const TEST_BUFFER = 500
|
||||
|
||||
var TEST_CONFIG = &mock.TestConfig{
|
||||
L1GethTestConfig: mock.L1GethTestConfig{
|
||||
HPort: 0,
|
||||
WPort: 8571,
|
||||
},
|
||||
L2GethTestConfig: mock.L2GethTestConfig{
|
||||
HPort: 0,
|
||||
WPort: 8567,
|
||||
},
|
||||
DbTestconfig: mock.DbTestconfig{
|
||||
DbName: "testwatcher_db",
|
||||
DbPort: 5438,
|
||||
DB_CONFIG: &db_config.DBConfig{
|
||||
DriverName: utils.GetEnvWithDefault("TEST_DB_DRIVER", "postgres"),
|
||||
DSN: utils.GetEnvWithDefault("TEST_DB_DSN", "postgres://postgres:123456@localhost:5438/testwatcher_db?sslmode=disable"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
// previousHeight store previous chain height
|
||||
previousHeight uint64
|
||||
l1gethImg docker.ImgInstance
|
||||
l2gethImg docker.ImgInstance
|
||||
dbImg docker.ImgInstance
|
||||
l2Backend *l2.Backend
|
||||
)
|
||||
|
||||
func setenv(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(l2db.GetDB().DB))
|
||||
defer l2db.Close()
|
||||
l1gethImg = mock.NewTestL1Docker(t, TEST_CONFIG)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1gethImg.Endpoint()
|
||||
l2Backend, l2gethImg, dbImg = mock.L2gethDocker(t, cfg, TEST_CONFIG)
|
||||
}
|
||||
|
||||
l2cfg := cfg.L2Config
|
||||
rc := l2.NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.ProofGenerationFreq, l2cfg.SkippedOpcodes, l2cfg.L2MessengerAddress, l2db)
|
||||
rc.Start()
|
||||
defer rc.Stop()
|
||||
|
||||
l1cfg := cfg.L1Config
|
||||
l1cfg.RelayerConfig.SenderConfig.Confirmations = 0
|
||||
newSender, err := sender.NewSender(context.Background(), l1cfg.RelayerConfig.SenderConfig, l1cfg.RelayerConfig.MessageSenderPrivateKeys)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create several transactions and commit to block
|
||||
numTransactions := 3
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
for i := 0; i < numTransactions; i++ {
|
||||
_, err = newSender.SendTransaction(strconv.Itoa(1000+i), &toAddress, big.NewInt(1000000000), nil)
|
||||
func TestWatcherFunction(t *testing.T) {
|
||||
setenv(t)
|
||||
t.Run("TestL2Backend", func(t *testing.T) {
|
||||
err := l2Backend.Start()
|
||||
assert.NoError(t, err)
|
||||
l2Backend.Stop()
|
||||
})
|
||||
t.Run("TestCreateNewWatcherAndStop", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
<-newSender.ConfirmChan()
|
||||
}
|
||||
|
||||
blockNum, err := l2Cli.BlockNumber(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, blockNum, uint64(numTransactions))
|
||||
}
|
||||
cfg.L2Config.Endpoint = l2gethImg.Endpoint()
|
||||
client, err := ethclient.Dial(cfg.L2Config.Endpoint)
|
||||
assert.NoError(t, err)
|
||||
mock.ClearDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
|
||||
func testMonitorBridgeContract(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
messengerABI, err := bridge_abi.L2MessengerMetaData.GetAbi()
|
||||
assert.NoError(t, err)
|
||||
|
||||
previousHeight, err := l2Cli.BlockNumber(context.Background())
|
||||
assert.NoError(t, err)
|
||||
l2db := mock.PrepareDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
|
||||
auth := prepareAuth(t, l2Cli, cfg.L2Config.RelayerConfig.MessageSenderPrivateKeys[0])
|
||||
skippedOpcodes := make(map[string]struct{}, len(cfg.L2Config.SkippedOpcodes))
|
||||
for _, op := range cfg.L2Config.SkippedOpcodes {
|
||||
skippedOpcodes[op] = struct{}{}
|
||||
}
|
||||
proofGenerationFreq := cfg.L2Config.ProofGenerationFreq
|
||||
if proofGenerationFreq == 0 {
|
||||
proofGenerationFreq = 1
|
||||
}
|
||||
rc := l2.NewL2WatcherClient(context.Background(), client, cfg.L2Config.Confirmations, proofGenerationFreq, skippedOpcodes, cfg.L2Config.L2MessengerAddress, messengerABI, l2db)
|
||||
rc.Start()
|
||||
|
||||
// deploy mock bridge
|
||||
_, tx, instance, err := mock_bridge.DeployMockBridge(auth, l2Cli)
|
||||
assert.NoError(t, err)
|
||||
address, err := bind.WaitDeployed(context.Background(), l2Cli, tx)
|
||||
assert.NoError(t, err)
|
||||
// Create several transactions and commit to block
|
||||
numTransactions := 3
|
||||
|
||||
rc := prepareRelayerClient(l2Cli, db, address)
|
||||
rc.Start()
|
||||
defer rc.Stop()
|
||||
for i := 0; i < numTransactions; i++ {
|
||||
tx := mock.SendTxToL2Client(t, client, cfg.L2Config.RelayerConfig.PrivateKey)
|
||||
// wait for mining
|
||||
_, err = bind.WaitMined(context.Background(), client, tx)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Call mock_bridge instance sendMessage to trigger emit events
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message := []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
receipt, err := bind.WaitMined(context.Background(), l2Cli, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
<-time.After(10 * time.Second)
|
||||
blockNum, err := client.BlockNumber(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, blockNum, uint64(numTransactions))
|
||||
|
||||
//extra block mined
|
||||
toAddress = common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message = []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
receipt, err = bind.WaitMined(context.Background(), l2Cli, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
rc.Stop()
|
||||
l2db.Close()
|
||||
})
|
||||
|
||||
// wait for dealing time
|
||||
<-time.After(6 * time.Second)
|
||||
t.Run("TestMonitorBridgeContract", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
t.Log("confirmations:", cfg.L2Config.Confirmations)
|
||||
|
||||
var latestHeight uint64
|
||||
latestHeight, err = l2Cli.BlockNumber(context.Background())
|
||||
assert.NoError(t, err)
|
||||
t.Log("Latest height is", latestHeight)
|
||||
cfg.L2Config.Endpoint = l2gethImg.Endpoint()
|
||||
client, err := ethclient.Dial(cfg.L2Config.Endpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check if we successfully stored events
|
||||
height, err := db.GetLayer2LatestWatchedHeight()
|
||||
assert.NoError(t, err)
|
||||
t.Log("Height in DB is", height)
|
||||
assert.Greater(t, height, int64(previousHeight))
|
||||
msgs, err := db.GetL2UnprocessedMessages()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(msgs))
|
||||
}
|
||||
mock.ClearDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
previousHeight, err = client.BlockNumber(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
func testFetchMultipleSentMessageInOneBlock(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(cfg.DBConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
defer db.Close()
|
||||
auth := prepareAuth(t, client, cfg.L2Config.RelayerConfig.PrivateKey)
|
||||
|
||||
previousHeight, err := l2Cli.BlockNumber(context.Background()) // shallow the global previousHeight
|
||||
assert.NoError(t, err)
|
||||
// deploy mock bridge
|
||||
_, tx, instance, err := mock_bridge.DeployMockBridge(auth, client)
|
||||
assert.NoError(t, err)
|
||||
address, err := bind.WaitDeployed(context.Background(), client, tx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
auth := prepareAuth(t, l2Cli, cfg.L2Config.RelayerConfig.MessageSenderPrivateKeys[0])
|
||||
db := mock.PrepareDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
rc := prepareRelayerClient(client, db, address)
|
||||
rc.Start()
|
||||
|
||||
_, trx, instance, err := mock_bridge.DeployMockBridge(auth, l2Cli)
|
||||
assert.NoError(t, err)
|
||||
address, err := bind.WaitDeployed(context.Background(), l2Cli, trx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rc := prepareRelayerClient(l2Cli, db, address)
|
||||
rc.Start()
|
||||
defer rc.Stop()
|
||||
|
||||
// Call mock_bridge instance sendMessage to trigger emit events multiple times
|
||||
numTransactions := 4
|
||||
var tx *types.Transaction
|
||||
|
||||
for i := 0; i < numTransactions; i++ {
|
||||
// Call mock_bridge instance sendMessage to trigger emit events
|
||||
addr := common.HexToAddress("0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63")
|
||||
nonce, nounceErr := l2Cli.PendingNonceAt(context.Background(), addr)
|
||||
nonce, err := client.PendingNonceAt(context.Background(), addr)
|
||||
assert.NoError(t, err)
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message := []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
receipt, err := bind.WaitMined(context.Background(), client, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
|
||||
//extra block mined
|
||||
addr = common.HexToAddress("0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63")
|
||||
nonce, nounceErr := client.PendingNonceAt(context.Background(), addr)
|
||||
assert.NoError(t, nounceErr)
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
toAddress = common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message = []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
receipt, err = bind.WaitMined(context.Background(), client, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
|
||||
// wait for dealing time
|
||||
<-time.After(6 * time.Second)
|
||||
|
||||
var latestHeight uint64
|
||||
latestHeight, err = client.BlockNumber(context.Background())
|
||||
assert.NoError(t, err)
|
||||
t.Log("Latest height is", latestHeight)
|
||||
|
||||
// check if we successfully stored events
|
||||
height, err := db.GetLayer2LatestWatchedHeight()
|
||||
assert.NoError(t, err)
|
||||
t.Log("Height in DB is", height)
|
||||
assert.Greater(t, height, int64(previousHeight))
|
||||
msgs, err := db.GetL2UnprocessedMessages()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(msgs))
|
||||
|
||||
rc.Stop()
|
||||
db.Close()
|
||||
})
|
||||
|
||||
t.Run("TestFetchMultipleSentMessageInOneBlock", func(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
cfg.L2Config.Endpoint = l2gethImg.Endpoint()
|
||||
client, err := ethclient.Dial(cfg.L2Config.Endpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mock.ClearDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
|
||||
previousHeight, err := client.BlockNumber(context.Background()) // shallow the global previousHeight
|
||||
assert.NoError(t, err)
|
||||
|
||||
auth := prepareAuth(t, client, cfg.L2Config.RelayerConfig.PrivateKey)
|
||||
|
||||
_, trx, instance, err := mock_bridge.DeployMockBridge(auth, client)
|
||||
assert.NoError(t, err)
|
||||
address, err := bind.WaitDeployed(context.Background(), client, trx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
db := mock.PrepareDB(t, TEST_CONFIG.DB_CONFIG)
|
||||
rc := prepareRelayerClient(client, db, address)
|
||||
rc.Start()
|
||||
|
||||
// Call mock_bridge instance sendMessage to trigger emit events multiple times
|
||||
numTransactions := 4
|
||||
var tx *types.Transaction
|
||||
|
||||
for i := 0; i < numTransactions; i++ {
|
||||
addr := common.HexToAddress("0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63")
|
||||
nonce, nounceErr := client.PendingNonceAt(context.Background(), addr)
|
||||
assert.NoError(t, nounceErr)
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message := []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
receipt, err := bind.WaitMined(context.Background(), client, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
|
||||
// extra block mined
|
||||
addr := common.HexToAddress("0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63")
|
||||
nonce, nounceErr := client.PendingNonceAt(context.Background(), addr)
|
||||
assert.NoError(t, nounceErr)
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message := []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
receipt, err = bind.WaitMined(context.Background(), client, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
|
||||
receipt, err := bind.WaitMined(context.Background(), l2Cli, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
// wait for dealing time
|
||||
<-time.After(6 * time.Second)
|
||||
|
||||
// extra block mined
|
||||
addr := common.HexToAddress("0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63")
|
||||
nonce, nounceErr := l2Cli.PendingNonceAt(context.Background(), addr)
|
||||
assert.NoError(t, nounceErr)
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
message := []byte("testbridgecontract")
|
||||
tx, err = instance.SendMessage(auth, toAddress, message, auth.GasPrice)
|
||||
assert.NoError(t, err)
|
||||
receipt, err = bind.WaitMined(context.Background(), l2Cli, tx)
|
||||
if receipt.Status != types.ReceiptStatusSuccessful || err != nil {
|
||||
t.Fatalf("Call failed")
|
||||
}
|
||||
// check if we successfully stored events
|
||||
height, err := db.GetLayer2LatestWatchedHeight()
|
||||
assert.NoError(t, err)
|
||||
t.Log("LatestHeight is", height)
|
||||
assert.Greater(t, height, int64(previousHeight)) // height must be greater than previousHeight because confirmations is 0
|
||||
msgs, err := db.GetL2UnprocessedMessages()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 5, len(msgs))
|
||||
|
||||
// wait for dealing time
|
||||
<-time.After(6 * time.Second)
|
||||
rc.Stop()
|
||||
db.Close()
|
||||
})
|
||||
|
||||
// Teardown
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, l1gethImg.Stop())
|
||||
assert.NoError(t, l2gethImg.Stop())
|
||||
assert.NoError(t, dbImg.Stop())
|
||||
})
|
||||
|
||||
// check if we successfully stored events
|
||||
height, err := db.GetLayer2LatestWatchedHeight()
|
||||
assert.NoError(t, err)
|
||||
t.Log("LatestHeight is", height)
|
||||
assert.Greater(t, height, int64(previousHeight)) // height must be greater than previousHeight because confirmations is 0
|
||||
msgs, err := db.GetL2UnprocessedMessages()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 5, len(msgs))
|
||||
}
|
||||
|
||||
func testTraceHasUnsupportedOpcodes(t *testing.T) {
|
||||
delegateTrace, err := os.ReadFile("../../common/testdata/blockTrace_delegate.json")
|
||||
func TestTraceHasUnsupportedOpcodes(t *testing.T) {
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
trace := &types.BlockTrace{}
|
||||
delegateTrace, err := os.ReadFile("../../common/testdata/blockResult_delegate.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
trace := &types.BlockResult{}
|
||||
assert.NoError(t, json.Unmarshal(delegateTrace, &trace))
|
||||
|
||||
assert.Equal(t, true, len(cfg.L2Config.SkippedOpcodes) == 2)
|
||||
_, exist := cfg.L2Config.SkippedOpcodes["DELEGATECALL"]
|
||||
assert.Equal(t, true, exist)
|
||||
|
||||
assert.True(t, l2.TraceHasUnsupportedOpcodes(cfg.L2Config.SkippedOpcodes, trace))
|
||||
unsupportedOpcodes := make(map[string]struct{})
|
||||
for _, val := range cfg.L2Config.SkippedOpcodes {
|
||||
unsupportedOpcodes[val] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareRelayerClient(l2Cli *ethclient.Client, db database.OrmFactory, contractAddr common.Address) *l2.WatcherClient {
|
||||
return l2.NewL2WatcherClient(context.Background(), l2Cli, 0, 1, map[string]struct{}{}, contractAddr, db)
|
||||
func prepareRelayerClient(client *ethclient.Client, db database.OrmFactory, contractAddr common.Address) *l2.WatcherClient {
|
||||
messengerABI, _ := bridge_abi.L1MessengerMetaData.GetAbi()
|
||||
return l2.NewL2WatcherClient(context.Background(), client, 0, 1, map[string]struct{}{}, contractAddr, messengerABI, db)
|
||||
}
|
||||
|
||||
func prepareAuth(t *testing.T, l2Cli *ethclient.Client, privateKey *ecdsa.PrivateKey) *bind.TransactOpts {
|
||||
func prepareAuth(t *testing.T, client *ethclient.Client, private string) *bind.TransactOpts {
|
||||
privateKey, err := crypto.HexToECDSA(private)
|
||||
assert.NoError(t, err)
|
||||
publicKey := privateKey.Public()
|
||||
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
|
||||
assert.True(t, ok)
|
||||
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
|
||||
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
|
||||
assert.NoError(t, err)
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(53077))
|
||||
assert.NoError(t, err)
|
||||
auth.Value = big.NewInt(0) // in wei
|
||||
assert.NoError(t, err)
|
||||
auth.GasPrice, err = l2Cli.SuggestGasPrice(context.Background())
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
auth.Value = big.NewInt(0) // in wei
|
||||
auth.GasLimit = uint64(30000000) // in units
|
||||
auth.GasPrice, err = client.SuggestGasPrice(context.Background())
|
||||
assert.NoError(t, err)
|
||||
return auth
|
||||
}
|
||||
|
||||
251
bridge/mock/mock.go
Normal file
251
bridge/mock/mock.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/migrate"
|
||||
|
||||
"scroll-tech/common/message"
|
||||
"scroll-tech/coordinator"
|
||||
coordinator_config "scroll-tech/coordinator/config"
|
||||
|
||||
bridge_config "scroll-tech/bridge/config"
|
||||
"scroll-tech/bridge/l2"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
|
||||
docker_db "scroll-tech/database/docker"
|
||||
)
|
||||
|
||||
// PerformHandshake sets up a websocket client to connect to the roller manager.
|
||||
func PerformHandshake(t *testing.T, c *websocket.Conn) {
|
||||
// Try to perform handshake
|
||||
pk, sk := generateKeyPair()
|
||||
authMsg := &message.AuthMessage{
|
||||
Identity: message.Identity{
|
||||
Name: "testRoller",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
PublicKey: common.Bytes2Hex(pk),
|
||||
},
|
||||
Signature: "",
|
||||
}
|
||||
|
||||
hash, err := authMsg.Identity.Hash()
|
||||
assert.NoError(t, err)
|
||||
sig, err := secp256k1.Sign(hash, sk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
authMsg.Signature = common.Bytes2Hex(sig)
|
||||
|
||||
b, err := json.Marshal(authMsg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := &message.Msg{
|
||||
Type: message.Register,
|
||||
Payload: b,
|
||||
}
|
||||
|
||||
b, err = json.Marshal(msg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, c.WriteMessage(websocket.BinaryMessage, b))
|
||||
}
|
||||
|
||||
func generateKeyPair() (pubkey, privkey []byte) {
|
||||
key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubkey = elliptic.Marshal(secp256k1.S256(), key.X, key.Y)
|
||||
|
||||
privkey = make([]byte, 32)
|
||||
blob := key.D.Bytes()
|
||||
copy(privkey[32-len(blob):], blob)
|
||||
|
||||
return pubkey, privkey
|
||||
}
|
||||
|
||||
// SetupMockVerifier sets up a mocked verifier for a test case.
|
||||
func SetupMockVerifier(t *testing.T, verifierEndpoint string) {
|
||||
err := os.RemoveAll(verifierEndpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
l, err := net.Listen("unix", verifierEndpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
conn, err := l.Accept()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Simply read all incoming messages and send a true boolean straight back.
|
||||
for {
|
||||
// Read length
|
||||
buf := make([]byte, 4)
|
||||
_, err = io.ReadFull(conn, buf)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Read message
|
||||
msgLength := binary.LittleEndian.Uint64(buf)
|
||||
buf = make([]byte, msgLength)
|
||||
_, err = io.ReadFull(conn, buf)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Return boolean
|
||||
buf = []byte{1}
|
||||
_, err = conn.Write(buf)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// L1GethTestConfig is the http and web socket port of l1geth docker
|
||||
type L1GethTestConfig struct {
|
||||
HPort int
|
||||
WPort int
|
||||
}
|
||||
|
||||
// L2GethTestConfig is the http and web socket port of l2geth docker
|
||||
type L2GethTestConfig struct {
|
||||
HPort int
|
||||
WPort int
|
||||
}
|
||||
|
||||
// DbTestconfig is the test config of database docker
|
||||
type DbTestconfig struct {
|
||||
DbName string
|
||||
DbPort int
|
||||
DB_CONFIG *database.DBConfig
|
||||
}
|
||||
|
||||
// TestConfig is the config for test
|
||||
type TestConfig struct {
|
||||
L1GethTestConfig
|
||||
L2GethTestConfig
|
||||
DbTestconfig
|
||||
}
|
||||
|
||||
// NewTestL1Docker starts and returns l1geth docker
|
||||
func NewTestL1Docker(t *testing.T, tcfg *TestConfig) docker.ImgInstance {
|
||||
img_geth := docker.NewImgGeth(t, "scroll_l1geth", "", "", tcfg.L1GethTestConfig.HPort, tcfg.L1GethTestConfig.WPort)
|
||||
assert.NoError(t, img_geth.Start())
|
||||
return img_geth
|
||||
}
|
||||
|
||||
// NewTestL2Docker starts and returns l2geth docker
|
||||
func NewTestL2Docker(t *testing.T, tcfg *TestConfig) docker.ImgInstance {
|
||||
img_geth := docker.NewImgGeth(t, "scroll_l2geth", "", "", tcfg.L2GethTestConfig.HPort, tcfg.L2GethTestConfig.WPort)
|
||||
assert.NoError(t, img_geth.Start())
|
||||
return img_geth
|
||||
}
|
||||
|
||||
// GetDbDocker starts and returns database docker
|
||||
func GetDbDocker(t *testing.T, tcfg *TestConfig) docker.ImgInstance {
|
||||
img_db := docker_db.NewImgDB(t, "postgres", "123456", tcfg.DbName, tcfg.DbPort)
|
||||
assert.NoError(t, img_db.Start())
|
||||
return img_db
|
||||
}
|
||||
|
||||
// L2gethDocker return mock l2geth client created with docker for test
|
||||
func L2gethDocker(t *testing.T, cfg *bridge_config.Config, tcfg *TestConfig) (*l2.Backend, docker.ImgInstance, docker.ImgInstance) {
|
||||
// initialize l2geth docker image
|
||||
img_geth := NewTestL2Docker(t, tcfg)
|
||||
|
||||
cfg.L2Config.Endpoint = img_geth.Endpoint()
|
||||
|
||||
// initialize db docker image
|
||||
img_db := GetDbDocker(t, tcfg)
|
||||
|
||||
db, err := database.NewOrmFactory(&database.DBConfig{
|
||||
DriverName: "postgres",
|
||||
DSN: img_db.Endpoint(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
client, err := l2.New(context.Background(), cfg.L2Config, db)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return client, img_geth, img_db
|
||||
}
|
||||
|
||||
// SetupRollerManager return coordinator.Manager for testcase
|
||||
func SetupRollerManager(t *testing.T, cfg *coordinator_config.Config, orm database.OrmFactory) *coordinator.Manager {
|
||||
// Load config file.
|
||||
ctx := context.Background()
|
||||
|
||||
SetupMockVerifier(t, cfg.RollerManagerConfig.VerifierEndpoint)
|
||||
rollerManager, err := coordinator.New(ctx, cfg.RollerManagerConfig, orm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Start rollermanager modules.
|
||||
err = rollerManager.Start()
|
||||
assert.NoError(t, err)
|
||||
return rollerManager
|
||||
}
|
||||
|
||||
// ClearDB clears db
|
||||
func ClearDB(t *testing.T, db_cfg *database.DBConfig) {
|
||||
factory, err := database.NewOrmFactory(db_cfg)
|
||||
assert.NoError(t, err)
|
||||
db := factory.GetDB()
|
||||
version0 := int64(0)
|
||||
err = migrate.Rollback(db.DB, &version0)
|
||||
assert.NoError(t, err)
|
||||
err = migrate.Migrate(db.DB)
|
||||
assert.NoError(t, err)
|
||||
err = db.DB.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// PrepareDB will return DB for testcase
|
||||
func PrepareDB(t *testing.T, db_cfg *database.DBConfig) database.OrmFactory {
|
||||
db, err := database.NewOrmFactory(db_cfg)
|
||||
assert.NoError(t, err)
|
||||
return db
|
||||
}
|
||||
|
||||
// SendTxToL2Client will send a default Tx by calling l2geth client
|
||||
func SendTxToL2Client(t *testing.T, client *ethclient.Client, private string) *types.Transaction {
|
||||
privateKey, err := crypto.HexToECDSA(private)
|
||||
assert.NoError(t, err)
|
||||
publicKey := privateKey.Public()
|
||||
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
|
||||
assert.True(t, ok)
|
||||
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
|
||||
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
|
||||
assert.NoError(t, err)
|
||||
value := big.NewInt(1000000000) // in wei
|
||||
gasLimit := uint64(30000000) // in units
|
||||
gasPrice, err := client.SuggestGasPrice(context.Background())
|
||||
assert.NoError(t, err)
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, nil)
|
||||
chainID, err := client.ChainID(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, client.SendTransaction(context.Background(), signedTx))
|
||||
return signedTx
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi/bind"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
)
|
||||
|
||||
type accountPool struct {
|
||||
client *ethclient.Client
|
||||
|
||||
minBalance *big.Int
|
||||
accounts map[common.Address]*bind.TransactOpts
|
||||
accsCh chan *bind.TransactOpts
|
||||
}
|
||||
|
||||
// newAccounts creates an accountPool instance.
|
||||
func newAccountPool(ctx context.Context, minBalance *big.Int, client *ethclient.Client, privs []*ecdsa.PrivateKey) (*accountPool, error) {
|
||||
if minBalance == nil {
|
||||
minBalance = big.NewInt(0)
|
||||
minBalance.SetString("100000000000000000000", 10)
|
||||
}
|
||||
accs := &accountPool{
|
||||
client: client,
|
||||
minBalance: minBalance,
|
||||
accounts: make(map[common.Address]*bind.TransactOpts, len(privs)),
|
||||
accsCh: make(chan *bind.TransactOpts, len(privs)+2),
|
||||
}
|
||||
|
||||
// get chainID from client
|
||||
chainID, err := client.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, privStr := range privs {
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(privStr, chainID)
|
||||
if err != nil {
|
||||
log.Error("failed to create account", "chainID", chainID.String(), "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set pending nonce
|
||||
nonce, err := client.PendingNonceAt(ctx, auth.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
accs.accounts[auth.From] = auth
|
||||
accs.accsCh <- auth
|
||||
}
|
||||
|
||||
return accs, accs.checkAndSetBalances(ctx)
|
||||
}
|
||||
|
||||
// getAccount get auth from channel.
|
||||
func (a *accountPool) getAccount() *bind.TransactOpts {
|
||||
select {
|
||||
case auth := <-a.accsCh:
|
||||
return auth
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// releaseAccount set used auth into channel.
|
||||
func (a *accountPool) releaseAccount(auth *bind.TransactOpts) {
|
||||
a.accsCh <- auth
|
||||
}
|
||||
|
||||
// reSetNonce reset nonce if send signed tx failed.
|
||||
func (a *accountPool) resetNonce(ctx context.Context, auth *bind.TransactOpts) {
|
||||
nonce, err := a.client.PendingNonceAt(ctx, auth.From)
|
||||
if err != nil {
|
||||
log.Warn("failed to reset nonce", "address", auth.From.String(), "err", err)
|
||||
return
|
||||
}
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
}
|
||||
|
||||
// checkAndSetBalance check balance and set min balance.
|
||||
func (a *accountPool) checkAndSetBalances(ctx context.Context) error {
|
||||
var (
|
||||
root *bind.TransactOpts
|
||||
maxBls = big.NewInt(0)
|
||||
lostAuths []*bind.TransactOpts
|
||||
)
|
||||
|
||||
for addr, auth := range a.accounts {
|
||||
bls, err := a.client.BalanceAt(ctx, addr, nil)
|
||||
if err != nil || bls.Cmp(a.minBalance) < 0 {
|
||||
if err != nil {
|
||||
log.Warn("failed to get balance", "address", addr.String(), "err", err)
|
||||
return err
|
||||
}
|
||||
lostAuths = append(lostAuths, auth)
|
||||
continue
|
||||
} else if bls.Cmp(maxBls) > 0 { // Find the biggest balance account.
|
||||
root, maxBls = auth, bls
|
||||
}
|
||||
}
|
||||
if root == nil {
|
||||
return fmt.Errorf("no account has enough balance")
|
||||
}
|
||||
if len(lostAuths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
tx *types.Transaction
|
||||
err error
|
||||
)
|
||||
for _, auth := range lostAuths {
|
||||
tx, err = a.createSignedTx(root, &auth.From, a.minBalance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = a.client.SendTransaction(ctx, tx)
|
||||
if err != nil {
|
||||
log.Error("Failed to send balance to account", "err", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("send balance to account", "account", auth.From.String(), "balance", a.minBalance.String())
|
||||
}
|
||||
// wait util mined
|
||||
if _, err = bind.WaitMined(ctx, a.client, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset root's nonce.
|
||||
a.resetNonce(ctx, root)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *accountPool) createSignedTx(from *bind.TransactOpts, to *common.Address, value *big.Int) (*types.Transaction, error) {
|
||||
gasPrice, err := a.client.SuggestGasPrice(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gasPrice.Mul(gasPrice, big.NewInt(2))
|
||||
|
||||
// Get pending nonce
|
||||
nonce, err := a.client.PendingNonceAt(context.Background(), from.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := types.NewTx(&types.LegacyTx{
|
||||
Nonce: nonce,
|
||||
To: to,
|
||||
Value: value,
|
||||
Gas: 500000,
|
||||
GasPrice: gasPrice,
|
||||
})
|
||||
signedTx, err := from.Signer(from.From, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signedTx, nil
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package sender
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
@@ -33,11 +32,6 @@ const (
|
||||
LegacyTxType = "LegacyTx"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoAvailableAccount indicates no available account error in the account pool.
|
||||
ErrNoAvailableAccount = errors.New("sender has no available account to send transaction")
|
||||
)
|
||||
|
||||
// DefaultSenderConfig The default config
|
||||
var DefaultSenderConfig = config.SenderConfig{
|
||||
Endpoint: "",
|
||||
@@ -79,21 +73,20 @@ type Sender struct {
|
||||
chainID *big.Int // The chain id of the endpoint
|
||||
ctx context.Context
|
||||
|
||||
// account fields.
|
||||
auths *accountPool
|
||||
|
||||
mu sync.Mutex
|
||||
auth *bind.TransactOpts
|
||||
blockNumber uint64 // Current block number on chain.
|
||||
baseFeePerGas uint64 // Current base fee per gas on chain
|
||||
pendingTxs sync.Map // Mapping from nonce to pending transaction
|
||||
confirmCh chan *Confirmation
|
||||
sendTxErrCh chan error
|
||||
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// NewSender returns a new instance of transaction sender
|
||||
// txConfirmationCh is used to notify confirmed transaction
|
||||
func NewSender(ctx context.Context, config *config.SenderConfig, privs []*ecdsa.PrivateKey) (*Sender, error) {
|
||||
func NewSender(ctx context.Context, config *config.SenderConfig, prv *ecdsa.PrivateKey) (*Sender, error) {
|
||||
if config == nil {
|
||||
config = &DefaultSenderConfig
|
||||
}
|
||||
@@ -108,10 +101,20 @@ func NewSender(ctx context.Context, config *config.SenderConfig, privs []*ecdsa.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auths, err := newAccountPool(ctx, config.MinBalance, client, privs)
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(prv, chainID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create account pool, err: %v", err)
|
||||
log.Error("failed to create account", "chainID", chainID.String(), "err", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Info("sender", "chainID", chainID.String(), "address", auth.From.String())
|
||||
|
||||
// set nonce
|
||||
nonce, err := client.PendingNonceAt(ctx, auth.From)
|
||||
if err != nil {
|
||||
log.Error("failed to get pending nonce", "address", auth.From.String(), "err", err)
|
||||
return nil, err
|
||||
}
|
||||
auth.Nonce = big.NewInt(int64(nonce))
|
||||
|
||||
// get header by number
|
||||
header, err := client.HeaderByNumber(ctx, nil)
|
||||
@@ -124,14 +127,15 @@ func NewSender(ctx context.Context, config *config.SenderConfig, privs []*ecdsa.
|
||||
config: config,
|
||||
client: client,
|
||||
chainID: chainID,
|
||||
auths: auths,
|
||||
auth: auth,
|
||||
sendTxErrCh: make(chan error, 4),
|
||||
confirmCh: make(chan *Confirmation, 128),
|
||||
baseFeePerGas: header.BaseFee.Uint64(),
|
||||
pendingTxs: sync.Map{},
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
go sender.loop(ctx)
|
||||
go sender.loop()
|
||||
|
||||
return sender, nil
|
||||
}
|
||||
@@ -147,14 +151,8 @@ func (s *Sender) ConfirmChan() <-chan *Confirmation {
|
||||
return s.confirmCh
|
||||
}
|
||||
|
||||
// NumberOfAccounts return the count of accounts.
|
||||
func (s *Sender) NumberOfAccounts() int {
|
||||
return len(s.auths.accounts)
|
||||
}
|
||||
|
||||
func (s *Sender) getFeeData(auth *bind.TransactOpts, target *common.Address, value *big.Int, data []byte) (*FeeData, error) {
|
||||
// estimate gas limit
|
||||
gasLimit, err := s.client.EstimateGas(s.ctx, ethereum.CallMsg{From: auth.From, To: target, Value: value, Data: data})
|
||||
func (s *Sender) getFeeData(msg ethereum.CallMsg) (*FeeData, error) {
|
||||
gasLimit, err := s.client.EstimateGas(s.ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -191,22 +189,28 @@ func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.I
|
||||
if _, ok := s.pendingTxs.Load(ID); ok {
|
||||
return common.Hash{}, fmt.Errorf("has the repeat tx ID, ID: %s", ID)
|
||||
}
|
||||
// get
|
||||
auth := s.auths.getAccount()
|
||||
if auth == nil {
|
||||
return common.Hash{}, ErrNoAvailableAccount
|
||||
}
|
||||
defer s.auths.releaseAccount(auth)
|
||||
|
||||
var (
|
||||
// estimate gas limit
|
||||
call = ethereum.CallMsg{
|
||||
From: s.auth.From,
|
||||
To: target,
|
||||
Gas: 0,
|
||||
GasPrice: nil,
|
||||
GasFeeCap: nil,
|
||||
GasTipCap: nil,
|
||||
Value: value,
|
||||
Data: data,
|
||||
AccessList: make(types.AccessList, 0),
|
||||
}
|
||||
feeData *FeeData
|
||||
tx *types.Transaction
|
||||
)
|
||||
|
||||
// estimate gas fee
|
||||
if feeData, err = s.getFeeData(auth, target, value, data); err != nil {
|
||||
if feeData, err = s.getFeeData(call); err != nil {
|
||||
return
|
||||
}
|
||||
if tx, err = s.createAndSendTx(auth, feeData, target, value, data); err == nil {
|
||||
if tx, err = s.createAndSendTx(feeData, target, value, data); err == nil {
|
||||
// add pending transaction to queue
|
||||
pending := &PendingTransaction{
|
||||
tx: tx,
|
||||
@@ -221,12 +225,12 @@ func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.I
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Sender) createAndSendTx(auth *bind.TransactOpts, feeData *FeeData, target *common.Address, value *big.Int, data []byte) (tx *types.Transaction, err error) {
|
||||
func (s *Sender) createAndSendTx(feeData *FeeData, target *common.Address, value *big.Int, data []byte) (tx *types.Transaction, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var (
|
||||
nonce = auth.Nonce.Uint64()
|
||||
nonce = s.auth.Nonce.Uint64()
|
||||
txData types.TxData
|
||||
)
|
||||
// lock here to avoit blocking when call `SuggestGasPrice`
|
||||
@@ -276,32 +280,24 @@ func (s *Sender) createAndSendTx(auth *bind.TransactOpts, feeData *FeeData, targ
|
||||
}
|
||||
|
||||
// sign and send
|
||||
tx, err = auth.Signer(auth.From, types.NewTx(txData))
|
||||
tx, err = s.auth.Signer(s.auth.From, types.NewTx(txData))
|
||||
if err != nil {
|
||||
log.Error("failed to sign tx", "err", err)
|
||||
return
|
||||
}
|
||||
if err = s.client.SendTransaction(s.ctx, tx); err != nil {
|
||||
log.Error("failed to send tx", "tx hash", tx.Hash().String(), "err", err)
|
||||
// Check if contain nonce, and reset nonce
|
||||
if strings.Contains(err.Error(), "nonce") {
|
||||
s.auths.resetNonce(context.Background(), auth)
|
||||
}
|
||||
s.sendTxErrCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
// update nonce
|
||||
auth.Nonce = big.NewInt(int64(nonce + 1))
|
||||
s.auth.Nonce = big.NewInt(int64(nonce + 1))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Sender) resubmitTransaction(feeData *FeeData, tx *types.Transaction) (*types.Transaction, error) {
|
||||
// Get a idle account from account pool.
|
||||
auth := s.auths.getAccount()
|
||||
if auth == nil {
|
||||
return nil, ErrNoAvailableAccount
|
||||
}
|
||||
defer s.auths.releaseAccount(auth)
|
||||
// @todo move query out of lock scope
|
||||
|
||||
escalateMultipleNum := new(big.Int).SetUint64(s.config.EscalateMultipleNum)
|
||||
escalateMultipleDen := new(big.Int).SetUint64(s.config.EscalateMultipleDen)
|
||||
@@ -338,7 +334,7 @@ func (s *Sender) resubmitTransaction(feeData *FeeData, tx *types.Transaction) (*
|
||||
feeData.gasTipCap = gasTipCap
|
||||
}
|
||||
|
||||
return s.createAndSendTx(auth, feeData, tx.To(), tx.Value(), tx.Data())
|
||||
return s.createAndSendTx(feeData, tx.To(), tx.Value(), tx.Data())
|
||||
}
|
||||
|
||||
// CheckPendingTransaction Check pending transaction given number of blocks to wait before confirmation.
|
||||
@@ -363,10 +359,7 @@ func (s *Sender) CheckPendingTransaction(header *types.Header) {
|
||||
var tx *types.Transaction
|
||||
tx, err := s.resubmitTransaction(pending.feeData, pending.tx)
|
||||
if err != nil {
|
||||
// If account pool is empty, it will try again in next loop.
|
||||
if !errors.Is(err, ErrNoAvailableAccount) {
|
||||
log.Error("failed to resubmit transaction, reset submitAt", "tx hash", pending.tx.Hash().String(), "err", err)
|
||||
}
|
||||
log.Error("failed to resubmit transaction, reset submitAt", "tx hash", pending.tx.Hash().String(), "err", err)
|
||||
} else {
|
||||
// flush submitAt
|
||||
pending.tx = tx
|
||||
@@ -378,13 +371,10 @@ func (s *Sender) CheckPendingTransaction(header *types.Header) {
|
||||
}
|
||||
|
||||
// Loop is the main event loop
|
||||
func (s *Sender) loop(ctx context.Context) {
|
||||
checkTick := time.NewTicker(time.Duration(s.config.CheckPendingTime) * time.Second)
|
||||
func (s *Sender) loop() {
|
||||
t := time.Duration(s.config.CheckPendingTime) * time.Second
|
||||
checkTick := time.NewTicker(t)
|
||||
defer checkTick.Stop()
|
||||
|
||||
checkBalanceTicker := time.NewTicker(time.Minute * 10)
|
||||
defer checkBalanceTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-checkTick.C:
|
||||
@@ -394,11 +384,17 @@ func (s *Sender) loop(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
s.CheckPendingTransaction(header)
|
||||
case <-checkBalanceTicker.C:
|
||||
// Check and set balance.
|
||||
_ = s.auths.checkAndSetBalances(ctx)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-s.sendTxErrCh:
|
||||
// redress nonce
|
||||
if strings.Contains(err.Error(), "nonce") {
|
||||
if nonce, err := s.client.PendingNonceAt(s.ctx, s.auth.From); err != nil {
|
||||
log.Error("failed to get pending nonce", "address", s.auth.From.String(), "err", err)
|
||||
} else {
|
||||
s.mu.Lock()
|
||||
s.auth.Nonce = big.NewInt(int64(nonce))
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
case <-s.stopCh:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package sender_test
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"testing"
|
||||
@@ -19,107 +18,102 @@ import (
|
||||
"scroll-tech/common/docker"
|
||||
|
||||
"scroll-tech/bridge/config"
|
||||
"scroll-tech/bridge/mock"
|
||||
"scroll-tech/bridge/sender"
|
||||
)
|
||||
|
||||
const TX_BATCH = 50
|
||||
const TX_BATCH = 100
|
||||
|
||||
var (
|
||||
privateKeys []*ecdsa.PrivateKey
|
||||
cfg *config.Config
|
||||
l2gethImg docker.ImgInstance
|
||||
TestConfig = &mock.TestConfig{
|
||||
L1GethTestConfig: mock.L1GethTestConfig{
|
||||
HPort: 0,
|
||||
WPort: 8576,
|
||||
},
|
||||
}
|
||||
|
||||
l1gethImg docker.ImgInstance
|
||||
private *ecdsa.PrivateKey
|
||||
)
|
||||
|
||||
func setupEnv(t *testing.T) {
|
||||
var err error
|
||||
cfg, err = config.NewConfig("../config.json")
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
priv, err := crypto.HexToECDSA("1212121212121212121212121212121212121212121212121212121212121212")
|
||||
prv, err := crypto.HexToECDSA(cfg.L2Config.RelayerConfig.PrivateKey)
|
||||
assert.NoError(t, err)
|
||||
// Load default private key.
|
||||
privateKeys = []*ecdsa.PrivateKey{priv}
|
||||
|
||||
l2gethImg = docker.NewTestL2Docker(t)
|
||||
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint = l2gethImg.Endpoint()
|
||||
private = prv
|
||||
l1gethImg = mock.NewTestL1Docker(t, TestConfig)
|
||||
}
|
||||
|
||||
func TestSender(t *testing.T) {
|
||||
func TestFunction(t *testing.T) {
|
||||
// Setup
|
||||
setupEnv(t)
|
||||
t.Run("test Run sender", func(t *testing.T) {
|
||||
// set config
|
||||
cfg, err := config.NewConfig("../config.json")
|
||||
assert.NoError(t, err)
|
||||
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = l1gethImg.Endpoint()
|
||||
|
||||
t.Run("test 1 account sender", func(t *testing.T) { testBatchSender(t, 1) })
|
||||
t.Run("test 3 account sender", func(t *testing.T) { testBatchSender(t, 3) })
|
||||
t.Run("test 8 account sender", func(t *testing.T) { testBatchSender(t, 8) })
|
||||
// create newSender
|
||||
newSender, err := sender.NewSender(context.Background(), cfg.L2Config.RelayerConfig.SenderConfig, private)
|
||||
assert.NoError(t, err)
|
||||
defer newSender.Stop()
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
// send transactions
|
||||
idCache := cmap.New()
|
||||
confirmCh := newSender.ConfirmChan()
|
||||
var (
|
||||
eg errgroup.Group
|
||||
errCh chan error
|
||||
)
|
||||
go func() {
|
||||
for i := 0; i < TX_BATCH; i++ {
|
||||
//toAddr := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
toAddr := common.BigToAddress(big.NewInt(int64(i + 1000)))
|
||||
id := strconv.Itoa(i + 1000)
|
||||
eg.Go(func() error {
|
||||
txHash, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil)
|
||||
if err != nil {
|
||||
t.Error("failed to send tx", "err", err)
|
||||
return err
|
||||
}
|
||||
t.Log("successful send a tx", "ID", id, "tx hash", txHash.String())
|
||||
idCache.Set(id, struct{}{})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
errCh <- eg.Wait()
|
||||
}()
|
||||
|
||||
// avoid 10 mins cause testcase panic
|
||||
after := time.After(60 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case cmsg := <-confirmCh:
|
||||
t.Log("get confirmations of", "ID: ", cmsg.ID, "status: ", cmsg.IsSuccessful)
|
||||
assert.Equal(t, true, cmsg.IsSuccessful)
|
||||
_, exist := idCache.Pop(cmsg.ID)
|
||||
assert.Equal(t, true, exist)
|
||||
// Receive all confirmed txs.
|
||||
if idCache.Count() == 0 {
|
||||
return
|
||||
}
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
t.Errorf("failed to send tx, err: %v", err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
case <-after:
|
||||
t.Logf("newSender test failed because timeout")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
})
|
||||
// Teardown
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, l2gethImg.Stop())
|
||||
assert.NoError(t, l1gethImg.Stop())
|
||||
})
|
||||
}
|
||||
|
||||
func testBatchSender(t *testing.T, batchSize int) {
|
||||
for len(privateKeys) < batchSize {
|
||||
priv, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privateKeys = append(privateKeys, priv)
|
||||
}
|
||||
|
||||
senderCfg := cfg.L1Config.RelayerConfig.SenderConfig
|
||||
senderCfg.Confirmations = 0
|
||||
newSender, err := sender.NewSender(context.Background(), senderCfg, privateKeys)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer newSender.Stop()
|
||||
|
||||
// send transactions
|
||||
var (
|
||||
eg errgroup.Group
|
||||
idCache = cmap.New()
|
||||
confirmCh = newSender.ConfirmChan()
|
||||
)
|
||||
for idx := 0; idx < newSender.NumberOfAccounts(); idx++ {
|
||||
index := idx
|
||||
eg.Go(func() error {
|
||||
for i := 0; i < TX_BATCH; i++ {
|
||||
toAddr := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
id := strconv.Itoa(i + index*1000)
|
||||
_, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil)
|
||||
if errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
<-time.After(time.Second)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idCache.Set(id, struct{}{})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("successful send batch txs, batch size: %d, total count: %d", newSender.NumberOfAccounts(), TX_BATCH*newSender.NumberOfAccounts())
|
||||
|
||||
// avoid 10 mins cause testcase panic
|
||||
after := time.After(80 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case cmsg := <-confirmCh:
|
||||
assert.Equal(t, true, cmsg.IsSuccessful)
|
||||
_, exist := idCache.Pop(cmsg.ID)
|
||||
assert.Equal(t, true, exist)
|
||||
// Receive all confirmed txs.
|
||||
if idCache.Count() == 0 {
|
||||
return
|
||||
}
|
||||
case <-after:
|
||||
t.Error("newSender test failed because of timeout")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
# Download Go dependencies
|
||||
FROM scrolltech/go-builder:1.18 as base
|
||||
# Build bridge in a stock Go builder container
|
||||
FROM scrolltech/go-builder:1.18 as builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.work* ./
|
||||
COPY ./bridge/go.* ./bridge/
|
||||
COPY ./common/go.* ./common/
|
||||
COPY ./coordinator/go.* ./coordinator/
|
||||
COPY ./database/go.* ./database/
|
||||
COPY ./roller/go.* ./roller/
|
||||
RUN go mod download -x
|
||||
COPY ./ /
|
||||
|
||||
# Build bridge
|
||||
FROM base as builder
|
||||
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
cd /src/bridge/cmd && go build -v -p 4 -o /bin/bridge
|
||||
RUN cd /bridge/cmd && go build -v -p 4 -o bridge
|
||||
|
||||
# Pull bridge into a second stage deploy alpine container
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=builder /bin/bridge /bin/
|
||||
COPY --from=builder /bridge/cmd/bridge /bin/
|
||||
|
||||
ENTRYPOINT ["bridge"]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
assets/
|
||||
coordinator/
|
||||
docs/
|
||||
integration-test/
|
||||
l2geth/
|
||||
roller/
|
||||
rpc-gateway/
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
# Download Go dependencies
|
||||
FROM scrolltech/go-builder:1.18 as base
|
||||
# Build scroll in a stock Go builder container
|
||||
FROM scrolltech/go-builder:1.18 as builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.work* ./
|
||||
COPY ./bridge/go.* ./bridge/
|
||||
COPY ./common/go.* ./common/
|
||||
COPY ./coordinator/go.* ./coordinator/
|
||||
COPY ./database/go.* ./database/
|
||||
COPY ./roller/go.* ./roller/
|
||||
RUN go mod download -x
|
||||
COPY ./ /
|
||||
|
||||
# Build coordinator
|
||||
FROM base as builder
|
||||
RUN cd /coordinator/cmd && go build -v -p 4 -o coordinator
|
||||
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
cd /src/coordinator/cmd && go build -v -p 4 -o /bin/coordinator
|
||||
|
||||
# Pull coordinator into a second stage deploy alpine container
|
||||
# Pull scroll into a second stage deploy alpine container
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=builder /bin/coordinator /bin/
|
||||
COPY --from=builder /coordinator/cmd/coordinator /bin/
|
||||
|
||||
ENTRYPOINT ["coordinator"]
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
assets/
|
||||
bridge/
|
||||
contracts/
|
||||
docs/
|
||||
integration-test/
|
||||
l2geth/
|
||||
roller/
|
||||
rpc-gateway/
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Download Go dependencies
|
||||
FROM scrolltech/go-builder:1.18 as base
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.work* ./
|
||||
COPY ./bridge/go.* ./bridge/
|
||||
COPY ./common/go.* ./common/
|
||||
COPY ./coordinator/go.* ./coordinator/
|
||||
COPY ./database/go.* ./database/
|
||||
COPY ./roller/go.* ./roller/
|
||||
RUN go mod download -x
|
||||
|
||||
# Build db_cli
|
||||
FROM base as builder
|
||||
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
cd /src/database/cmd && go build -v -p 4 -o /bin/db_cli
|
||||
|
||||
# Pull db_cli into a second stage deploy alpine container
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=builder /bin/db_cli /bin/
|
||||
|
||||
ENTRYPOINT ["db_cli"]
|
||||
@@ -1,6 +0,0 @@
|
||||
assets/
|
||||
contracts/
|
||||
docs/
|
||||
integration-test/
|
||||
l2geth/
|
||||
rpc-gateway/
|
||||
@@ -6,8 +6,7 @@ RUN apk add --no-cache gcc musl-dev linux-headers git ca-certificates
|
||||
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
PATH=/usr/local/cargo/bin:$PATH
|
||||
|
||||
RUN set -eux; \
|
||||
apkArch="$(apk --print-arch)"; \
|
||||
|
||||
@@ -6,13 +6,11 @@ ARG DEFAULT_RUST_TOOLCHAIN=nightly-2022-08-23
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
gcc \
|
||||
git \
|
||||
musl-dev
|
||||
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
PATH=/usr/local/cargo/bin:$PATH
|
||||
|
||||
RUN set -eux; \
|
||||
apkArch="$(apk --print-arch)"; \
|
||||
|
||||
@@ -2,22 +2,12 @@ package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var verbose bool
|
||||
|
||||
func init() {
|
||||
v := os.Getenv("LOG_DOCKER")
|
||||
if v == "true" || v == "TRUE" {
|
||||
verbose = true
|
||||
}
|
||||
}
|
||||
|
||||
type checkFunc func(buf string)
|
||||
|
||||
// Cmd struct
|
||||
@@ -37,12 +27,12 @@ func NewCmd(t *testing.T) *Cmd {
|
||||
//stdout: bytes.Buffer{},
|
||||
errMsg: make(chan error, 2),
|
||||
}
|
||||
// Handle panic.
|
||||
cmd.RegistFunc("panic", func(buf string) {
|
||||
if strings.Contains(buf, "panic") {
|
||||
cmd.errMsg <- errors.New(buf)
|
||||
}
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -75,11 +65,7 @@ func (t *Cmd) ErrMsg() <-chan error {
|
||||
|
||||
func (t *Cmd) Write(data []byte) (int, error) {
|
||||
out := string(data)
|
||||
if verbose {
|
||||
t.Logf(out)
|
||||
} else if strings.Contains(out, "error") || strings.Contains(out, "warning") {
|
||||
t.Logf(out)
|
||||
}
|
||||
t.Logf(out)
|
||||
go func(content string) {
|
||||
t.checkFuncs.Range(func(key, value interface{}) bool {
|
||||
check := value.(checkFunc)
|
||||
|
||||
@@ -9,8 +9,22 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
var (
|
||||
cli *client.Client
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
cli, err = client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cli.NegotiateAPIVersion(context.Background())
|
||||
}
|
||||
|
||||
// ImgGeth the geth image manager include l1geth and l2geth.
|
||||
type ImgGeth struct {
|
||||
image string
|
||||
@@ -30,7 +44,7 @@ type ImgGeth struct {
|
||||
func NewImgGeth(t *testing.T, image, volume, ipc string, hPort, wPort int) ImgInstance {
|
||||
return &ImgGeth{
|
||||
image: image,
|
||||
name: fmt.Sprintf("%s-%d", image, time.Now().Nanosecond()),
|
||||
name: fmt.Sprintf("%s-%d", image, time.Now().Unix()),
|
||||
volume: volume,
|
||||
ipcPath: ipc,
|
||||
httpPort: hPort,
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq" //nolint:golint
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -41,13 +39,3 @@ func TestL2Geth(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
t.Logf("chainId: %s", chainID.String())
|
||||
}
|
||||
|
||||
func TestDB(t *testing.T) {
|
||||
driverName := "postgres"
|
||||
dbImg := NewTestDBDocker(t, driverName)
|
||||
defer dbImg.Stop()
|
||||
|
||||
db, err := sqlx.Open(driverName, dbImg.Endpoint())
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, db.Ping())
|
||||
}
|
||||
|
||||
@@ -5,22 +5,8 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
var (
|
||||
cli *client.Client
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
cli, err = client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cli.NegotiateAPIVersion(context.Background())
|
||||
}
|
||||
|
||||
// ImgInstance is an img instance.
|
||||
type ImgInstance interface {
|
||||
Start() error
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM scrolltech/l2geth:prealpha-v4.2
|
||||
FROM scrolltech/l2geth:prealpha-v2.2
|
||||
|
||||
RUN mkdir -p /l2geth/keystore
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
l1StartPort = 10000
|
||||
l2StartPort = 20000
|
||||
dbStartPort = 30000
|
||||
)
|
||||
|
||||
// NewTestL1Docker starts and returns l1geth docker
|
||||
func NewTestL1Docker(t *testing.T) ImgInstance {
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
imgL1geth := NewImgGeth(t, "scroll_l1geth", "", "", 0, l1StartPort+int(id.Int64()))
|
||||
assert.NoError(t, imgL1geth.Start())
|
||||
return imgL1geth
|
||||
}
|
||||
|
||||
// NewTestL2Docker starts and returns l2geth docker
|
||||
func NewTestL2Docker(t *testing.T) ImgInstance {
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
imgL2geth := NewImgGeth(t, "scroll_l2geth", "", "", 0, l2StartPort+int(id.Int64()))
|
||||
assert.NoError(t, imgL2geth.Start())
|
||||
return imgL2geth
|
||||
}
|
||||
|
||||
// NewTestDBDocker starts and returns database docker
|
||||
func NewTestDBDocker(t *testing.T, driverName string) ImgInstance {
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
imgDB := NewImgDB(t, driverName, "123456", "test_db", dbStartPort+int(id.Int64()))
|
||||
assert.NoError(t, imgDB.Start())
|
||||
return imgDB
|
||||
}
|
||||
@@ -4,13 +4,10 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
)
|
||||
|
||||
@@ -19,7 +16,6 @@ require (
|
||||
github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
|
||||
github.com/btcsuite/btcd v0.20.1-beta // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||
@@ -49,7 +45,6 @@ require (
|
||||
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
|
||||
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.14 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
@@ -64,10 +59,7 @@ require (
|
||||
github.com/prometheus/tsdb v0.7.1 // indirect
|
||||
github.com/rjeczalik/notify v0.9.1 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/scroll-tech/zktrie v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
|
||||
@@ -86,7 +86,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
|
||||
github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -153,8 +152,6 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@@ -252,8 +249,6 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -289,9 +284,6 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
|
||||
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
@@ -312,9 +304,6 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
@@ -385,19 +374,15 @@ github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRr
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6 h1:o0Gq2d8nus6x82apluA5RJwjlOca7LIlpAfxlyvQvxs=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6/go.mod h1:jurIpDQ0hqtp9//xxeWzr8X9KMP/+TYn+vz3K1wZrv0=
|
||||
github.com/scroll-tech/zktrie v0.3.0 h1:c0GRNELUyAtyuiwllQKT1XjNbs7NRGfguKouiyLfFNY=
|
||||
github.com/scroll-tech/zktrie v0.3.0/go.mod h1:CuJFlG1/soTJJBAySxCZgTF7oPvd5qF6utHOEciC43Q=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d h1:eh1i1M9BKPCQckNFQsV/yfazQ895hevkWr8GuqhKNrk=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d/go.mod h1:SkQ1431r0LkrExTELsapw6JQHYpki8O1mSsObTSmBWU=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
@@ -433,7 +418,6 @@ github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
|
||||
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
|
||||
@@ -15,16 +15,16 @@ import (
|
||||
type MsgType uint16
|
||||
|
||||
const (
|
||||
// ErrorMsgType error type of message.
|
||||
ErrorMsgType MsgType = iota
|
||||
// RegisterMsgType register type of message, sent by a roller when a connection is established.
|
||||
RegisterMsgType
|
||||
// TaskMsgType task type of message, sent by a sequencer to a roller to notify them
|
||||
// Error message.
|
||||
Error MsgType = iota
|
||||
// Register message, sent by a roller when a connection is established.
|
||||
Register
|
||||
// BlockTrace message, sent by a sequencer to a roller to notify them
|
||||
// they need to generate a proof.
|
||||
TaskMsgType
|
||||
// ProofMsgType proof type of message, sent by a roller to a sequencer when they have finished
|
||||
BlockTrace
|
||||
// Proof message, sent by a roller to a sequencer when they have finished
|
||||
// proof generation of a given set of block traces.
|
||||
ProofMsgType
|
||||
Proof
|
||||
)
|
||||
|
||||
// RespStatus is the status of the proof generation
|
||||
@@ -98,10 +98,14 @@ func (i *Identity) Hash() ([]byte, error) {
|
||||
return hash[:], nil
|
||||
}
|
||||
|
||||
// TaskMsg is a wrapper type around db ProveTask type.
|
||||
type TaskMsg struct {
|
||||
ID string `json:"id"`
|
||||
Traces []*types.BlockTrace `json:"blockTraces"`
|
||||
// BlockTraces is a wrapper type around types.BlockResult which adds an ID
|
||||
// that identifies which proof generation session these block traces are
|
||||
// associated to. This then allows the roller to add the ID back to their
|
||||
// proof message once generated, and in turn helps the sequencer understand
|
||||
// where to handle the proof.
|
||||
type BlockTraces struct {
|
||||
ID uint64 `json:"id"`
|
||||
Traces *types.BlockResult `json:"blockTraces"`
|
||||
}
|
||||
|
||||
// ProofMsg is the message received from rollers that contains zk proof, the status of
|
||||
@@ -109,8 +113,8 @@ type TaskMsg struct {
|
||||
type ProofMsg struct {
|
||||
Status RespStatus `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ID string `json:"id"`
|
||||
// FIXME: Maybe we need to allow Proof omitempty
|
||||
ID uint64 `json:"id"`
|
||||
// FIXME: Maybe we need to allow Proof omitempty? Also in scroll.
|
||||
Proof *AggProof `json:"proof"`
|
||||
}
|
||||
|
||||
|
||||
21936
common/testdata/blockResult_delegate.json
vendored
Normal file
21936
common/testdata/blockResult_delegate.json
vendored
Normal file
File diff suppressed because one or more lines are too long
3305
common/testdata/blockResult_orm.json
vendored
Normal file
3305
common/testdata/blockResult_orm.json
vendored
Normal file
File diff suppressed because one or more lines are too long
21936
common/testdata/blockResult_relayer.json
vendored
Normal file
21936
common/testdata/blockResult_relayer.json
vendored
Normal file
File diff suppressed because one or more lines are too long
21940
common/testdata/blockResult_relayer_parent.json
vendored
Normal file
21940
common/testdata/blockResult_relayer_parent.json
vendored
Normal file
File diff suppressed because one or more lines are too long
544
common/testdata/blockTrace_02.json
vendored
544
common/testdata/blockTrace_02.json
vendored
@@ -1,544 +0,0 @@
|
||||
{
|
||||
"coinbase": {
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 2,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffd5a5fa703d6a00d4dd70",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
"header": {
|
||||
"parentHash": "0xe17f08d25ef61a8ee12aa29704b901345a597f5e45a9a0f603ae0f70845b54dc",
|
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"miner": "0x0000000000000000000000000000000000000000",
|
||||
"stateRoot": "0x25b792bfd6d6456451f996e9383225e026fff469da205bb916768c0a78fd16af",
|
||||
"transactionsRoot": "0x3057754c197f33e1fe799e996db6232b5257412feea05b3c1754738f0b33fe32",
|
||||
"receiptsRoot": "0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4",
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"difficulty": "0x2",
|
||||
"number": "0x2",
|
||||
"gasLimit": "0x355418d1e8184",
|
||||
"gasUsed": "0xa410",
|
||||
"timestamp": "0x63807b2a",
|
||||
"extraData": "0xd983010a0d846765746889676f312e31372e3133856c696e75780000000000004b54a94f0df14333e63c8a13dfe6097c1a08b5fd2c225a8dc0f199dae245aead55d6f774a980a0c925be407748d56a14106afda7ddc1dec342e7ee3b0d58a8df01",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": "0x0000000000000000",
|
||||
"baseFeePerGas": "0x1de9",
|
||||
"hash": "0xc7b6c7022c8386cdaf6fcd3d4f8d03dce257ae3664a072fdce511ecefce73ad0"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"type": 0,
|
||||
"nonce": 0,
|
||||
"txHash": "0xb2febc1213baec968f6575789108e175273b8da8f412468098893084229f1542",
|
||||
"gas": 500000,
|
||||
"gasPrice": "0x3b9aec2e",
|
||||
"from": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"to": "0xc0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca2",
|
||||
"chainId": "0xcf55",
|
||||
"value": "0x152d02c7e14af6000000",
|
||||
"data": "0x",
|
||||
"isCreate": false,
|
||||
"v": "0x19ece",
|
||||
"r": "0xab07ae99c67aa78e7ba5cf6781e90cc32b219b1de102513d56548a41e86df514",
|
||||
"s": "0x34cbd19feacd73e8ce64d00c4d1996b9b5243c578fd7f51bfaec288bbaf42a8b"
|
||||
},
|
||||
{
|
||||
"type": 0,
|
||||
"nonce": 1,
|
||||
"txHash": "0xe6ac2ffc543d07f1e280912a2abe3aa659bf83773740681151297ada1bb211dd",
|
||||
"gas": 500000,
|
||||
"gasPrice": "0x3b9aec2e",
|
||||
"from": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"to": "0x01bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed",
|
||||
"chainId": "0xcf55",
|
||||
"value": "0x152d02c7e14af6000000",
|
||||
"data": "0x",
|
||||
"isCreate": false,
|
||||
"v": "0x19ece",
|
||||
"r": "0xf039985866d8256f10c1be4f7b2cace28d8f20bde27e2604393eb095b7f77316",
|
||||
"s": "0x5a3e6e81065f2b4604bcec5bd4aba684835996fc3f879380aac1c09c6eed32f1"
|
||||
}
|
||||
],
|
||||
"storageTrace": {
|
||||
"rootBefore": "0x2579122e8f9ec1e862e7d415cef2fb495d7698a8e5f0dddc5651ba4236336e7d",
|
||||
"rootAfter": "0x25b792bfd6d6456451f996e9383225e026fff469da205bb916768c0a78fd16af",
|
||||
"proofs": {
|
||||
"0x01bae6BF68E9A03Fb2bc0615b1bf0d69ce9411eD": [
|
||||
"0x01204920151d7e3cd9d1b5ba09d3ad6ea157c82d1cc425731f209e71a007165a9c0404000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000000201c5a77d9fa7ef466951b2f01f724bca3a5820b63000000000000000000000000",
|
||||
"0x5448495320495320534f4d45204d4147494320425954455320464f5220534d54206d3172525867503278704449"
|
||||
],
|
||||
"0x1C5A77d9FA7eF466951B2F01F724BCa3A5820b63": [
|
||||
"0x01204920151d7e3cd9d1b5ba09d3ad6ea157c82d1cc425731f209e71a007165a9c0404000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000000201c5a77d9fa7ef466951b2f01f724bca3a5820b63000000000000000000000000",
|
||||
"0x5448495320495320534f4d45204d4147494320425954455320464f5220534d54206d3172525867503278704449"
|
||||
],
|
||||
"0xc0c4C8bAEA3f6Acb49b6E1fb9e2ADEcEeaCB0cA2": [
|
||||
"0x01204920151d7e3cd9d1b5ba09d3ad6ea157c82d1cc425731f209e71a007165a9c0404000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000000201c5a77d9fa7ef466951b2f01f724bca3a5820b63000000000000000000000000",
|
||||
"0x5448495320495320534f4d45204d4147494320425954455320464f5220534d54206d3172525867503278704449"
|
||||
]
|
||||
}
|
||||
},
|
||||
"executionResults": [
|
||||
{
|
||||
"gas": 21000,
|
||||
"failed": false,
|
||||
"returnValue": "",
|
||||
"from": {
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 0,
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
"to": {
|
||||
"address": "0xc0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca2",
|
||||
"nonce": 0,
|
||||
"balance": "0x0",
|
||||
"codeHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"accountAfter": [
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 1,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffead2fd381eb5006a6eb8",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"address": "0xc0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca2",
|
||||
"nonce": 0,
|
||||
"balance": "0x152d02c7e14af6000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 1,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffead2fd381eb5006a6eb8",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"structLogs": []
|
||||
},
|
||||
{
|
||||
"gas": 21000,
|
||||
"failed": false,
|
||||
"returnValue": "",
|
||||
"from": {
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 1,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffead2fd381eb5006a6eb8",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
"to": {
|
||||
"address": "0x01bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed",
|
||||
"nonce": 0,
|
||||
"balance": "0x0",
|
||||
"codeHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"accountAfter": [
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 2,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffd5a5fa703d6a00d4dd70",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"address": "0x01bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed",
|
||||
"nonce": 0,
|
||||
"balance": "0x152d02c7e14af6000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"nonce": 2,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffd5a5fa703d6a00d4dd70",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"structLogs": []
|
||||
}
|
||||
],
|
||||
"mptwitness": [
|
||||
{
|
||||
"address": "0x01bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed",
|
||||
"accountKey": "0x7f53dc37d5a264eb72d8ae1a31c82239a385d9f6df23b81c48e97862d6d92314",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x7d6e333642ba5156dcddf0e5a898765d49fbf2ce15d4e762e8c19e8f2e127925",
|
||||
"leaf": {
|
||||
"value": "0xdf92dc6c0dd1c7fde78079ea62863977463f07e542966c6393f4d8cd6cce3117",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x7d6e333642ba5156dcddf0e5a898765d49fbf2ce15d4e762e8c19e8f2e127925",
|
||||
"leaf": {
|
||||
"value": "0xdf92dc6c0dd1c7fde78079ea62863977463f07e542966c6393f4d8cd6cce3117",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"accountKey": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x7d6e333642ba5156dcddf0e5a898765d49fbf2ce15d4e762e8c19e8f2e127925",
|
||||
"leaf": {
|
||||
"value": "0xdf92dc6c0dd1c7fde78079ea62863977463f07e542966c6393f4d8cd6cce3117",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0xf6b9a9f1e25add11bf5d0705e58f4b7a968b281ec23a8d41e719a0e27d87450c",
|
||||
"leaf": {
|
||||
"value": "0x716491d19f5e25dc565d05bbde1f30b343b1489b2d923feb30141d24a87c0a00",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
{
|
||||
"nonce": 0,
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"nonce": 2,
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0xc0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca2",
|
||||
"accountKey": "0x9b38091c0e341793f0e755a1ea7b64bfb06455aced31334598fcfd02d1d94616",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0xf6b9a9f1e25add11bf5d0705e58f4b7a968b281ec23a8d41e719a0e27d87450c",
|
||||
"leaf": {
|
||||
"value": "0x716491d19f5e25dc565d05bbde1f30b343b1489b2d923feb30141d24a87c0a00",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0xf6b9a9f1e25add11bf5d0705e58f4b7a968b281ec23a8d41e719a0e27d87450c",
|
||||
"leaf": {
|
||||
"value": "0x716491d19f5e25dc565d05bbde1f30b343b1489b2d923feb30141d24a87c0a00",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x01bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed",
|
||||
"accountKey": "0x7f53dc37d5a264eb72d8ae1a31c82239a385d9f6df23b81c48e97862d6d92314",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0xf6b9a9f1e25add11bf5d0705e58f4b7a968b281ec23a8d41e719a0e27d87450c",
|
||||
"leaf": {
|
||||
"value": "0x716491d19f5e25dc565d05bbde1f30b343b1489b2d923feb30141d24a87c0a00",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0xf6b9a9f1e25add11bf5d0705e58f4b7a968b281ec23a8d41e719a0e27d87450c",
|
||||
"leaf": {
|
||||
"value": "0x716491d19f5e25dc565d05bbde1f30b343b1489b2d923feb30141d24a87c0a00",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"accountKey": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0xf6b9a9f1e25add11bf5d0705e58f4b7a968b281ec23a8d41e719a0e27d87450c",
|
||||
"leaf": {
|
||||
"value": "0x716491d19f5e25dc565d05bbde1f30b343b1489b2d923feb30141d24a87c0a00",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806",
|
||||
"leaf": {
|
||||
"value": "0xf199fe1a085b5bb134e90d0bfdaf70579fa703ab3db986a6730b44cfd5207b15",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
{
|
||||
"nonce": 2,
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"nonce": 2,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffd5a5fa703d6a00d4dd70",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0xc0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca2",
|
||||
"accountKey": "0x9b38091c0e341793f0e755a1ea7b64bfb06455aced31334598fcfd02d1d94616",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806",
|
||||
"leaf": {
|
||||
"value": "0xf199fe1a085b5bb134e90d0bfdaf70579fa703ab3db986a6730b44cfd5207b15",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806",
|
||||
"leaf": {
|
||||
"value": "0xf199fe1a085b5bb134e90d0bfdaf70579fa703ab3db986a6730b44cfd5207b15",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x01bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed",
|
||||
"accountKey": "0x7f53dc37d5a264eb72d8ae1a31c82239a385d9f6df23b81c48e97862d6d92314",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806",
|
||||
"leaf": {
|
||||
"value": "0xf199fe1a085b5bb134e90d0bfdaf70579fa703ab3db986a6730b44cfd5207b15",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x1",
|
||||
"root": "0x06954857b2b6569c7dfe8380f8c7fe72d6b7fefca206b1fe74dc6ffbf97c132e",
|
||||
"path": [
|
||||
{
|
||||
"value": "0x1b9da0b70b242af37d53f5bda27315b2dbd178f6b4b1e026be43cab8d46b850b",
|
||||
"sibling": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806"
|
||||
}
|
||||
],
|
||||
"leaf": {
|
||||
"value": "0x45c70c4b7345dd1705ed019271dd1d7fbe2a1054ecefaf3fd2a22388a483072e",
|
||||
"sibling": "0x7f53dc37d5a264eb72d8ae1a31c82239a385d9f6df23b81c48e97862d6d92314"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
null,
|
||||
{
|
||||
"nonce": 0,
|
||||
"balance": "0x152d02c7e14af6000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x1c5a77d9fa7ef466951b2f01f724bca3a5820b63",
|
||||
"accountKey": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x06954857b2b6569c7dfe8380f8c7fe72d6b7fefca206b1fe74dc6ffbf97c132e",
|
||||
"path": [
|
||||
{
|
||||
"value": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806",
|
||||
"sibling": "0x1b9da0b70b242af37d53f5bda27315b2dbd178f6b4b1e026be43cab8d46b850b"
|
||||
}
|
||||
],
|
||||
"leaf": {
|
||||
"value": "0xf199fe1a085b5bb134e90d0bfdaf70579fa703ab3db986a6730b44cfd5207b15",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x0",
|
||||
"root": "0x06954857b2b6569c7dfe8380f8c7fe72d6b7fefca206b1fe74dc6ffbf97c132e",
|
||||
"path": [
|
||||
{
|
||||
"value": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806",
|
||||
"sibling": "0x1b9da0b70b242af37d53f5bda27315b2dbd178f6b4b1e026be43cab8d46b850b"
|
||||
}
|
||||
],
|
||||
"leaf": {
|
||||
"value": "0xf199fe1a085b5bb134e90d0bfdaf70579fa703ab3db986a6730b44cfd5207b15",
|
||||
"sibling": "0x9c5a1607a0719e201f7325c41c2dc857a16eadd309bab5d1d93c7e1d15204920"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
{
|
||||
"nonce": 2,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffd5a5fa703d6a00d4dd70",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
},
|
||||
{
|
||||
"nonce": 2,
|
||||
"balance": "0x1ffffffffffffffffffffffffffffffffffffffffffd5a5fa703d6a00d4dd70",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0xc0c4c8baea3f6acb49b6e1fb9e2adeceeacb0ca2",
|
||||
"accountKey": "0x9b38091c0e341793f0e755a1ea7b64bfb06455aced31334598fcfd02d1d94616",
|
||||
"accountPath": [
|
||||
{
|
||||
"pathPart": "0x1",
|
||||
"root": "0x06954857b2b6569c7dfe8380f8c7fe72d6b7fefca206b1fe74dc6ffbf97c132e",
|
||||
"path": [
|
||||
{
|
||||
"value": "0x1b9da0b70b242af37d53f5bda27315b2dbd178f6b4b1e026be43cab8d46b850b",
|
||||
"sibling": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806"
|
||||
}
|
||||
],
|
||||
"leaf": {
|
||||
"value": "0x45c70c4b7345dd1705ed019271dd1d7fbe2a1054ecefaf3fd2a22388a483072e",
|
||||
"sibling": "0x7f53dc37d5a264eb72d8ae1a31c82239a385d9f6df23b81c48e97862d6d92314"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pathPart": "0x3",
|
||||
"root": "0xaf16fd780a8c7616b95b20da69f4ff26e0253238e996f9516445d6d6bf92b725",
|
||||
"path": [
|
||||
{
|
||||
"value": "0x5bbe97e7e66485b203f9dfea64eb7fa7df06959b12cbde2beba14f8f91133a13",
|
||||
"sibling": "0x34f20c09876841ab1c180877223cc915ca96589b05ecea552aa2b3b9b47de806"
|
||||
},
|
||||
{
|
||||
"value": "0x2e591357b02ab3117c35ad94a4e1a724fdbd95d6463da1f6c8017e6d000ecf02",
|
||||
"sibling": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
{
|
||||
"value": "0x794953bb5d8aa00f90383ff435ce2ea58e30e1da1061e69455c38496766ec10f",
|
||||
"sibling": "0x1b9da0b70b242af37d53f5bda27315b2dbd178f6b4b1e026be43cab8d46b850b"
|
||||
}
|
||||
],
|
||||
"leaf": {
|
||||
"value": "0x45c70c4b7345dd1705ed019271dd1d7fbe2a1054ecefaf3fd2a22388a483072e",
|
||||
"sibling": "0x9b38091c0e341793f0e755a1ea7b64bfb06455aced31334598fcfd02d1d94616"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accountUpdate": [
|
||||
null,
|
||||
{
|
||||
"nonce": 0,
|
||||
"balance": "0x152d02c7e14af6000000",
|
||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
}
|
||||
],
|
||||
"commonStateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"statePath": [
|
||||
null,
|
||||
null
|
||||
],
|
||||
"stateUpdate": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
12876
common/testdata/blockTrace_03.json
vendored
12876
common/testdata/blockTrace_03.json
vendored
File diff suppressed because one or more lines are too long
993
common/testdata/blockTrace_delegate.json
vendored
993
common/testdata/blockTrace_delegate.json
vendored
File diff suppressed because one or more lines are too long
@@ -1,18 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// ComputeBatchID compute a unique hash for a batch using "endBlockHash" & "endBlockHash in last batch"
|
||||
// & "batch height", following the logic in `_computeBatchId` in contracts/src/L1/rollup/ZKRollup.sol
|
||||
func ComputeBatchID(endBlockHash common.Hash, lastEndBlockHash common.Hash, index *big.Int) string {
|
||||
return crypto.Keccak256Hash(
|
||||
endBlockHash.Bytes(),
|
||||
lastEndBlockHash.Bytes(),
|
||||
index.Bytes(),
|
||||
).String()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/math"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
)
|
||||
|
||||
func TestComputeBatchID(t *testing.T) {
|
||||
// expected generated using contract:
|
||||
// ```
|
||||
// // SPDX-License-Identifier: MIT
|
||||
|
||||
// pragma solidity ^0.6.6;
|
||||
|
||||
// contract AAA {
|
||||
// uint256 private constant MAX = ~uint256(0);
|
||||
|
||||
// function _computeBatchId() public pure returns (bytes32) {
|
||||
// return keccak256(abi.encode(bytes32(0), bytes32(0), MAX));
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
|
||||
expected := "0xafe1e714d2cd3ed5b0fa0a04ee95cd564b955ab8661c5665588758b48b66e263"
|
||||
actual := utils.ComputeBatchID(common.Hash{}, common.Hash{}, math.MaxBig256)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package utils
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var (
|
||||
// CommonFlags is used for app common flags in different modules
|
||||
CommonFlags = []cli.Flag{
|
||||
&ConfigFileFlag,
|
||||
&VerbosityFlag,
|
||||
&LogFileFlag,
|
||||
&LogJSONFormat,
|
||||
&LogDebugFlag,
|
||||
}
|
||||
// ConfigFileFlag load json type config file.
|
||||
ConfigFileFlag = cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "JSON configuration file",
|
||||
Value: "./config.json",
|
||||
}
|
||||
// VerbosityFlag log level.
|
||||
VerbosityFlag = cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail",
|
||||
Value: 3,
|
||||
}
|
||||
// LogFileFlag decides where the logger output is sent. If this flag is left
|
||||
// empty, it will log to stdout.
|
||||
LogFileFlag = cli.StringFlag{
|
||||
Name: "log.file",
|
||||
Usage: "Tells the module where to write log entries",
|
||||
}
|
||||
// LogJSONFormat decides the log format is json or not
|
||||
LogJSONFormat = cli.BoolFlag{
|
||||
Name: "log.json",
|
||||
Usage: "Tells the module whether log format is json or not",
|
||||
Value: true,
|
||||
}
|
||||
// LogDebugFlag make log messages with call-site location
|
||||
LogDebugFlag = cli.BoolFlag{
|
||||
Name: "log.debug",
|
||||
Usage: "Prepends log messages with call-site location (file and line number)",
|
||||
}
|
||||
)
|
||||
@@ -1,44 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/keystore"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
)
|
||||
|
||||
// LoadOrCreateKey load or create keystore by keystorePath, keystorePath cannot be a dir.
|
||||
func LoadOrCreateKey(keystorePath string, keystorePassword string) (*ecdsa.PrivateKey, error) {
|
||||
if fi, err := os.Stat(keystorePath); os.IsNotExist(err) {
|
||||
// If there is no keystore, make a new one.
|
||||
ks := keystore.NewKeyStore(filepath.Dir(keystorePath), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
account, kerr := ks.NewAccount(keystorePassword)
|
||||
if kerr != nil {
|
||||
return nil, fmt.Errorf("generate crypto account failed %v", kerr)
|
||||
}
|
||||
|
||||
err = os.Rename(account.URL.Path, keystorePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("create a new account", "address", account.Address.Hex())
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else if fi.IsDir() {
|
||||
return nil, fmt.Errorf("keystorePath cannot be a dir")
|
||||
}
|
||||
|
||||
keyjson, err := os.ReadFile(filepath.Clean(keystorePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := keystore.DecryptKey(keyjson, keystorePassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key.PrivateKey, nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var keyDir = "key-dir"
|
||||
|
||||
func TestLoadOrCreateKey(t *testing.T) {
|
||||
err := os.RemoveAll(keyDir)
|
||||
assert.NoError(t, err)
|
||||
// no dir.
|
||||
ksPath := filepath.Join(keyDir, "my-key")
|
||||
_, err = LoadOrCreateKey(ksPath, "pwd")
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(ksPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// only has dir, no file.
|
||||
err = os.MkdirAll(keyDir, os.ModeDir)
|
||||
assert.NoError(t, err)
|
||||
_, err = LoadOrCreateKey(ksPath, "pwd")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// load keystore
|
||||
_, err = LoadOrCreateKey(ksPath, "pwd")
|
||||
assert.NoError(t, err)
|
||||
os.RemoveAll(keyDir)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var tag = "prealpha-v6.2"
|
||||
var tag = "prealpha-v4.1"
|
||||
|
||||
var commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
|
||||
@@ -8,8 +8,7 @@ integration-test
|
||||
lib
|
||||
|- forge-std - "foundry dependency"
|
||||
scripts
|
||||
|- deploy_xxx.ts - "hardhat deploy script"
|
||||
|- foundry - "foundry deploy scripts"
|
||||
|- deploy.ts - "hardhat deploy script"
|
||||
src
|
||||
|- test
|
||||
| `- xxx.t.sol - "Unit testi in solidity"
|
||||
|
||||
@@ -38,38 +38,13 @@ Append a cross chain message to message queue.
|
||||
|---|---|---|
|
||||
| _0 | uint256 | undefined |
|
||||
|
||||
### batches
|
||||
|
||||
```solidity
|
||||
function batches(bytes32) external view returns (bytes32 batchHash, bytes32 parentHash, uint64 batchIndex, bool verified)
|
||||
```
|
||||
|
||||
Mapping from batch id to batch struct.
|
||||
|
||||
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _0 | bytes32 | undefined |
|
||||
|
||||
#### Returns
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| batchHash | bytes32 | undefined |
|
||||
| parentHash | bytes32 | undefined |
|
||||
| batchIndex | uint64 | undefined |
|
||||
| verified | bool | undefined |
|
||||
|
||||
### blocks
|
||||
|
||||
```solidity
|
||||
function blocks(bytes32) external view returns (bytes32 parentHash, bytes32 transactionRoot, uint64 blockHeight, uint64 batchIndex)
|
||||
function blocks(bytes32) external view returns (struct IZKRollup.BlockHeader header, bytes32 transactionRoot, bool verified)
|
||||
```
|
||||
|
||||
Mapping from block hash to block struct.
|
||||
Mapping from block hash to block index.
|
||||
|
||||
|
||||
|
||||
@@ -83,15 +58,14 @@ Mapping from block hash to block struct.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| parentHash | bytes32 | undefined |
|
||||
| header | IZKRollup.BlockHeader | undefined |
|
||||
| transactionRoot | bytes32 | undefined |
|
||||
| blockHeight | uint64 | undefined |
|
||||
| batchIndex | uint64 | undefined |
|
||||
| verified | bool | undefined |
|
||||
|
||||
### commitBatch
|
||||
### commitBlock
|
||||
|
||||
```solidity
|
||||
function commitBatch(IZKRollup.Layer2Batch _batch) external nonpayable
|
||||
function commitBlock(IZKRollup.BlockHeader _header, IZKRollup.Layer2Transaction[] _txn) external nonpayable
|
||||
```
|
||||
|
||||
|
||||
@@ -102,15 +76,16 @@ function commitBatch(IZKRollup.Layer2Batch _batch) external nonpayable
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batch | IZKRollup.Layer2Batch | undefined |
|
||||
| _header | IZKRollup.BlockHeader | undefined |
|
||||
| _txn | IZKRollup.Layer2Transaction[] | undefined |
|
||||
|
||||
### finalizeBatchWithProof
|
||||
### finalizeBlockWithProof
|
||||
|
||||
```solidity
|
||||
function finalizeBatchWithProof(bytes32 _batchId, uint256[] _proof, uint256[] _instances) external nonpayable
|
||||
function finalizeBlockWithProof(bytes32 _blockHash, uint256[] _proof, uint256[] _instances) external nonpayable
|
||||
```
|
||||
|
||||
finalize commited batch in layer 1
|
||||
finalize commited block in layer 1
|
||||
|
||||
*will add more parameters if needed.*
|
||||
|
||||
@@ -118,17 +93,17 @@ finalize commited batch in layer 1
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchId | bytes32 | The identification of the commited batch. |
|
||||
| _proof | uint256[] | The corresponding proof of the commited batch. |
|
||||
| _instances | uint256[] | Instance used to verify, generated from batch. |
|
||||
| _blockHash | bytes32 | The block hash of the commited block. |
|
||||
| _proof | uint256[] | The corresponding proof of the commited block. |
|
||||
| _instances | uint256[] | Instance used to verify, generated from block. |
|
||||
|
||||
### finalizedBatches
|
||||
### finalizedBlocks
|
||||
|
||||
```solidity
|
||||
function finalizedBatches(uint256) external view returns (bytes32)
|
||||
function finalizedBlocks(uint256) external view returns (bytes32)
|
||||
```
|
||||
|
||||
Mapping from batch index to finalized batch id.
|
||||
Mapping from block height to finalized block hash.
|
||||
|
||||
|
||||
|
||||
@@ -203,7 +178,7 @@ Return the total number of appended message.
|
||||
### importGenesisBlock
|
||||
|
||||
```solidity
|
||||
function importGenesisBlock(IZKRollup.Layer2BlockHeader _genesis) external nonpayable
|
||||
function importGenesisBlock(IZKRollup.BlockHeader _genesis) external nonpayable
|
||||
```
|
||||
|
||||
|
||||
@@ -214,7 +189,7 @@ function importGenesisBlock(IZKRollup.Layer2BlockHeader _genesis) external nonpa
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _genesis | IZKRollup.Layer2BlockHeader | undefined |
|
||||
| _genesis | IZKRollup.BlockHeader | undefined |
|
||||
|
||||
### initialize
|
||||
|
||||
@@ -232,13 +207,13 @@ function initialize(uint256 _chainId) external nonpayable
|
||||
|---|---|---|
|
||||
| _chainId | uint256 | undefined |
|
||||
|
||||
### lastFinalizedBatchID
|
||||
### lastFinalizedBlockHash
|
||||
|
||||
```solidity
|
||||
function lastFinalizedBatchID() external view returns (bytes32)
|
||||
function lastFinalizedBlockHash() external view returns (bytes32)
|
||||
```
|
||||
|
||||
The latest finalized batch id.
|
||||
The hash of the latest finalized block.
|
||||
|
||||
|
||||
|
||||
@@ -350,21 +325,21 @@ function renounceOwnership() external nonpayable
|
||||
*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
|
||||
|
||||
|
||||
### revertBatch
|
||||
### revertBlock
|
||||
|
||||
```solidity
|
||||
function revertBatch(bytes32 _batchId) external nonpayable
|
||||
function revertBlock(bytes32 _blockHash) external nonpayable
|
||||
```
|
||||
|
||||
revert a pending batch.
|
||||
revert a pending block.
|
||||
|
||||
*one can only revert unfinalized batches.*
|
||||
*one can only revert unfinalized blocks.*
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchId | bytes32 | The identification of the batch. |
|
||||
| _blockHash | bytes32 | The block hash of the block. |
|
||||
|
||||
### transferOwnership
|
||||
|
||||
@@ -417,7 +392,7 @@ Update the address of operator.
|
||||
### verifyMessageStateProof
|
||||
|
||||
```solidity
|
||||
function verifyMessageStateProof(uint256 _batchIndex, uint256 _blockHeight) external view returns (bool)
|
||||
function verifyMessageStateProof(uint256 _blockNumber) external view returns (bool)
|
||||
```
|
||||
|
||||
Verify a state proof for message relay.
|
||||
@@ -428,8 +403,7 @@ Verify a state proof for message relay.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchIndex | uint256 | undefined |
|
||||
| _blockHeight | uint256 | undefined |
|
||||
| _blockNumber | uint256 | undefined |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -441,13 +415,13 @@ Verify a state proof for message relay.
|
||||
|
||||
## Events
|
||||
|
||||
### CommitBatch
|
||||
### CommitBlock
|
||||
|
||||
```solidity
|
||||
event CommitBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash)
|
||||
event CommitBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight, bytes32 _parentHash)
|
||||
```
|
||||
|
||||
Emitted when a new batch is commited.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -455,18 +429,17 @@ Emitted when a new batch is commited.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchId `indexed` | bytes32 | undefined |
|
||||
| _batchHash | bytes32 | undefined |
|
||||
| _batchIndex | uint256 | undefined |
|
||||
| _blockHash `indexed` | bytes32 | undefined |
|
||||
| _blockHeight `indexed` | uint64 | undefined |
|
||||
| _parentHash | bytes32 | undefined |
|
||||
|
||||
### FinalizeBatch
|
||||
### FinalizeBlock
|
||||
|
||||
```solidity
|
||||
event FinalizeBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash)
|
||||
event FinalizeBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight)
|
||||
```
|
||||
|
||||
Emitted when a batch is finalized.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -474,10 +447,8 @@ Emitted when a batch is finalized.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchId `indexed` | bytes32 | undefined |
|
||||
| _batchHash | bytes32 | undefined |
|
||||
| _batchIndex | uint256 | undefined |
|
||||
| _parentHash | bytes32 | undefined |
|
||||
| _blockHash `indexed` | bytes32 | undefined |
|
||||
| _blockHeight `indexed` | uint64 | undefined |
|
||||
|
||||
### OwnershipTransferred
|
||||
|
||||
@@ -496,13 +467,13 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn
|
||||
| previousOwner `indexed` | address | undefined |
|
||||
| newOwner `indexed` | address | undefined |
|
||||
|
||||
### RevertBatch
|
||||
### RevertBlock
|
||||
|
||||
```solidity
|
||||
event RevertBatch(bytes32 indexed _batchId)
|
||||
event RevertBlock(bytes32 indexed _blockHash)
|
||||
```
|
||||
|
||||
Emitted when a batch is reverted.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -510,7 +481,7 @@ Emitted when a batch is reverted.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchId `indexed` | bytes32 | undefined |
|
||||
| _blockHash `indexed` | bytes32 | undefined |
|
||||
|
||||
### UpdateMesssenger
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ describe("ERC20Gateway", async () => {
|
||||
gasUsed: 0,
|
||||
timestamp: 0,
|
||||
extraData: "0x",
|
||||
txs: [],
|
||||
});
|
||||
|
||||
// deploy L1ScrollMessenger in layer 1
|
||||
@@ -426,7 +425,7 @@ describe("ERC20Gateway", async () => {
|
||||
deadline,
|
||||
nonce,
|
||||
messageData,
|
||||
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
|
||||
{ blockNumber: 0, merkleProof: "0x" }
|
||||
);
|
||||
await relayTx.wait();
|
||||
// should emit RelayedMessage
|
||||
@@ -478,7 +477,7 @@ describe("ERC20Gateway", async () => {
|
||||
deadline,
|
||||
nonce,
|
||||
messageData,
|
||||
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
|
||||
{ blockNumber: 0, merkleProof: "0x" }
|
||||
);
|
||||
await relayTx.wait();
|
||||
// should emit RelayedMessage
|
||||
@@ -745,7 +744,7 @@ describe("ERC20Gateway", async () => {
|
||||
deadline,
|
||||
nonce,
|
||||
messageData,
|
||||
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
|
||||
{ blockNumber: 0, merkleProof: "0x" }
|
||||
);
|
||||
await relayTx.wait();
|
||||
const afterBalanceLayer1 = await l1WETH.balanceOf(recipient.address);
|
||||
@@ -802,7 +801,7 @@ describe("ERC20Gateway", async () => {
|
||||
deadline,
|
||||
nonce,
|
||||
messageData,
|
||||
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
|
||||
{ blockNumber: 0, merkleProof: "0x" }
|
||||
);
|
||||
await relayTx.wait();
|
||||
const afterBalanceLayer1 = await l1WETH.balanceOf(recipient.address);
|
||||
|
||||
@@ -43,7 +43,6 @@ describe("GatewayRouter", async () => {
|
||||
gasUsed: 0,
|
||||
timestamp: 0,
|
||||
extraData: "0x",
|
||||
txs: []
|
||||
});
|
||||
|
||||
// deploy L1ScrollMessenger in layer 1
|
||||
@@ -196,7 +195,7 @@ describe("GatewayRouter", async () => {
|
||||
deadline,
|
||||
nonce,
|
||||
messageData,
|
||||
{ batchIndex: 0, blockHeight: 0, merkleProof: "0x" }
|
||||
{ blockNumber: 0, merkleProof: "0x" }
|
||||
);
|
||||
await relayTx.wait();
|
||||
const afterBalanceLayer1 = await ethers.provider.getBalance(recipient.address);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Deployment scripts of Scroll contracts
|
||||
|
||||
## Deployment using Hardhat
|
||||
|
||||
The scripts should run as below sequence:
|
||||
|
||||
```bash
|
||||
@@ -63,38 +61,4 @@ env CONTRACT_NAME=L2ERC721Gateway CONTRACT_OWNER=$owner npx hardhat run --networ
|
||||
env CONTRACT_NAME=L2ERC1155Gateway CONTRACT_OWNER=$owner npx hardhat run --network $layer2 scripts/transfer_ownership.ts
|
||||
```
|
||||
|
||||
Reference testnet [run_deploy_contracts.sh](https://github.com/scroll-tech/testnet/blob/staging/run_deploy_contracts.sh) for details.
|
||||
|
||||
## Deployment using Foundry
|
||||
|
||||
Note: The Foundry scripts take parameters like `CHAIN_ID_L2` and `L1_ZK_ROLLUP_PROXY_ADDR` as environment variables.
|
||||
|
||||
```bash
|
||||
# allexport
|
||||
$ set -a
|
||||
|
||||
$ cat .env
|
||||
CHAIN_ID_L2="5343541"
|
||||
SCROLL_L1_RPC="http://localhost:8543"
|
||||
SCROLL_L2_RPC="http://localhost:8545"
|
||||
L1_DEPLOYER_PRIVATE_KEY="0x0000000000000000000000000000000000000000000000000000000000000001"
|
||||
L2_DEPLOYER_PRIVATE_KEY="0x0000000000000000000000000000000000000000000000000000000000000002"
|
||||
L1_ROLLUP_OPERATOR_ADDR="0x1111111111111111111111111111111111111111"
|
||||
|
||||
$ source .env
|
||||
|
||||
# Deploy L1 contracts
|
||||
# Note: We extract the logged addresses as environment variables.
|
||||
$ OUTPUT=$(forge script scripts/foundry/DeployL1BridgeContracts.s.sol:DeployL1BridgeContracts --rpc-url $SCROLL_L1_RPC --broadcast); echo $OUTPUT
|
||||
$ echo "$OUTPUT" | grep -Eo "(L1)_.*" > .env.l1_addresses
|
||||
$ source .env.l1_addresses
|
||||
|
||||
# Deploy L2 contracts
|
||||
$ OUTPUT=$(forge script scripts/foundry/DeployL2BridgeContracts.s.sol:DeployL2BridgeContracts --rpc-url $SCROLL_L2_RPC --broadcast); echo $OUTPUT
|
||||
$ echo "$OUTPUT" | grep -Eo "(L2)_.*" > .env.l2_addresses
|
||||
$ source .env.l2_addresses
|
||||
|
||||
# Initialize contracts
|
||||
$ forge script scripts/foundry/InitializeL1BridgeContracts.s.sol:InitializeL1BridgeContracts --rpc-url $SCROLL_L1_RPC --broadcast
|
||||
$ forge script scripts/foundry/InitializeL2BridgeContracts.s.sol:InitializeL2BridgeContracts --rpc-url $SCROLL_L2_RPC --broadcast
|
||||
```
|
||||
Reference testnet [run.sh](https://github.com/scroll-tech/testnet/blob/main/run.sh) for details.
|
||||
|
||||
@@ -7,8 +7,7 @@ import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
|
||||
interface IL1ScrollMessenger is IScrollMessenger {
|
||||
struct L2MessageProof {
|
||||
// @todo add more fields
|
||||
uint256 batchIndex;
|
||||
uint256 blockHeight;
|
||||
uint256 blockNumber;
|
||||
bytes merkleProof;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ contract L1ScrollMessenger is OwnableUpgradeable, PausableUpgradeable, ScrollMes
|
||||
require(!isMessageExecuted[_msghash], "Message successfully executed");
|
||||
|
||||
// @todo check proof
|
||||
require(IZKRollup(rollup).verifyMessageStateProof(_proof.batchIndex, _proof.blockHeight), "invalid state proof");
|
||||
require(IZKRollup(rollup).verifyMessageStateProof(_proof.blockNumber), "invalid state proof");
|
||||
require(ZkTrieVerifier.verifyMerkleProof(_proof.merkleProof), "invalid proof");
|
||||
|
||||
// @todo check `_to` address to avoid attack.
|
||||
|
||||
@@ -5,21 +5,11 @@ pragma solidity ^0.8.0;
|
||||
interface IZKRollup {
|
||||
/**************************************** Events ****************************************/
|
||||
|
||||
/// @notice Emitted when a new batch is commited.
|
||||
/// @param _batchHash The hash of the batch
|
||||
/// @param _batchIndex The index of the batch
|
||||
/// @param _parentHash The hash of parent batch
|
||||
event CommitBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash);
|
||||
event CommitBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight, bytes32 _parentHash);
|
||||
|
||||
/// @notice Emitted when a batch is reverted.
|
||||
/// @param _batchId The identification of the batch.
|
||||
event RevertBatch(bytes32 indexed _batchId);
|
||||
event RevertBlock(bytes32 indexed _blockHash);
|
||||
|
||||
/// @notice Emitted when a batch is finalized.
|
||||
/// @param _batchHash The hash of the batch
|
||||
/// @param _batchIndex The index of the batch
|
||||
/// @param _parentHash The hash of parent batch
|
||||
event FinalizeBatch(bytes32 indexed _batchId, bytes32 _batchHash, uint256 _batchIndex, bytes32 _parentHash);
|
||||
event FinalizeBlock(bytes32 indexed _blockHash, uint64 indexed _blockHeight);
|
||||
|
||||
/// @dev The transanction struct
|
||||
struct Layer2Transaction {
|
||||
@@ -30,14 +20,10 @@ interface IZKRollup {
|
||||
uint256 gasPrice;
|
||||
uint256 value;
|
||||
bytes data;
|
||||
// signature
|
||||
uint256 r;
|
||||
uint256 s;
|
||||
uint64 v;
|
||||
}
|
||||
|
||||
/// @dev The block header struct
|
||||
struct Layer2BlockHeader {
|
||||
struct BlockHeader {
|
||||
bytes32 blockHash;
|
||||
bytes32 parentHash;
|
||||
uint256 baseFee;
|
||||
@@ -46,15 +32,6 @@ interface IZKRollup {
|
||||
uint64 gasUsed;
|
||||
uint64 timestamp;
|
||||
bytes extraData;
|
||||
Layer2Transaction[] txs;
|
||||
}
|
||||
|
||||
/// @dev The batch struct, the batch hash is always the last block hash of `blocks`.
|
||||
struct Layer2Batch {
|
||||
uint64 batchIndex;
|
||||
// The hash of the last block in the parent batch
|
||||
bytes32 parentHash;
|
||||
Layer2BlockHeader[] blocks;
|
||||
}
|
||||
|
||||
/**************************************** View Functions ****************************************/
|
||||
@@ -72,7 +49,7 @@ interface IZKRollup {
|
||||
|
||||
/// @notice Verify a state proof for message relay.
|
||||
/// @dev add more fields.
|
||||
function verifyMessageStateProof(uint256 _batchIndex, uint256 _blockHeight) external view returns (bool);
|
||||
function verifyMessageStateProof(uint256 _blockNumber) external view returns (bool);
|
||||
|
||||
/**************************************** Mutated Functions ****************************************/
|
||||
|
||||
@@ -95,23 +72,24 @@ interface IZKRollup {
|
||||
uint256 _gasLimit
|
||||
) external returns (uint256);
|
||||
|
||||
/// @notice commit a batch in layer 1
|
||||
/// @dev store in a more compacted form later.
|
||||
/// @param _batch The layer2 batch to commit.
|
||||
function commitBatch(Layer2Batch memory _batch) external;
|
||||
|
||||
/// @notice revert a pending batch.
|
||||
/// @dev one can only revert unfinalized batches.
|
||||
/// @param _batchId The identification of the batch.
|
||||
function revertBatch(bytes32 _batchId) external;
|
||||
|
||||
/// @notice finalize commited batch in layer 1
|
||||
/// @notice commit block in layer 1
|
||||
/// @dev will add more parameters if needed.
|
||||
/// @param _batchId The identification of the commited batch.
|
||||
/// @param _proof The corresponding proof of the commited batch.
|
||||
/// @param _instances Instance used to verify, generated from batch.
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 _batchId,
|
||||
/// @param _header The block header.
|
||||
/// @param _txns The transactions included in the block.
|
||||
function commitBlock(BlockHeader memory _header, Layer2Transaction[] memory _txns) external;
|
||||
|
||||
/// @notice revert a pending block.
|
||||
/// @dev one can only revert unfinalized blocks.
|
||||
/// @param _blockHash The block hash of the block.
|
||||
function revertBlock(bytes32 _blockHash) external;
|
||||
|
||||
/// @notice finalize commited block in layer 1
|
||||
/// @dev will add more parameters if needed.
|
||||
/// @param _blockHash The block hash of the commited block.
|
||||
/// @param _proof The corresponding proof of the commited block.
|
||||
/// @param _instances Instance used to verify, generated from block.
|
||||
function finalizeBlockWithProof(
|
||||
bytes32 _blockHash,
|
||||
uint256[] memory _proof,
|
||||
uint256[] memory _instances
|
||||
) external;
|
||||
|
||||
@@ -7,8 +7,6 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O
|
||||
import { IZKRollup } from "./IZKRollup.sol";
|
||||
import { RollupVerifier } from "../../libraries/verifier/RollupVerifier.sol";
|
||||
|
||||
// solhint-disable reason-string
|
||||
|
||||
/// @title ZKRollup
|
||||
/// @notice This contract maintains essential data for zk rollup, including:
|
||||
///
|
||||
@@ -31,17 +29,10 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
|
||||
|
||||
/**************************************** Variables ****************************************/
|
||||
|
||||
struct Layer2BlockStored {
|
||||
bytes32 parentHash;
|
||||
struct Block {
|
||||
// @todo simplify fields later
|
||||
BlockHeader header;
|
||||
bytes32 transactionRoot;
|
||||
uint64 blockHeight;
|
||||
uint64 batchIndex;
|
||||
}
|
||||
|
||||
struct Layer2BatchStored {
|
||||
bytes32 batchHash;
|
||||
bytes32 parentHash;
|
||||
uint64 batchIndex;
|
||||
bool verified;
|
||||
}
|
||||
|
||||
@@ -61,17 +52,14 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
|
||||
/// @dev The list of appended message hash.
|
||||
bytes32[] private messageQueue;
|
||||
|
||||
/// @notice The latest finalized batch id.
|
||||
bytes32 public lastFinalizedBatchID;
|
||||
/// @notice The hash of the latest finalized block.
|
||||
bytes32 public lastFinalizedBlockHash;
|
||||
|
||||
/// @notice Mapping from block hash to block struct.
|
||||
mapping(bytes32 => Layer2BlockStored) public blocks;
|
||||
/// @notice Mapping from block hash to block index.
|
||||
mapping(bytes32 => Block) public blocks;
|
||||
|
||||
/// @notice Mapping from batch id to batch struct.
|
||||
mapping(bytes32 => Layer2BatchStored) public batches;
|
||||
|
||||
/// @notice Mapping from batch index to finalized batch id.
|
||||
mapping(uint256 => bytes32) public finalizedBatches;
|
||||
/// @notice Mapping from block height to finalized block hash.
|
||||
mapping(uint256 => bytes32) public finalizedBlocks;
|
||||
|
||||
modifier OnlyOperator() {
|
||||
// @todo In the decentralize mode, it should be only called by a list of validator.
|
||||
@@ -111,18 +99,12 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
|
||||
}
|
||||
|
||||
/// @inheritdoc IZKRollup
|
||||
function verifyMessageStateProof(uint256 _batchIndex, uint256 _blockHeight) external view returns (bool) {
|
||||
bytes32 _batchId = finalizedBatches[_batchIndex];
|
||||
// check if batch is verified
|
||||
if (_batchId == bytes32(0)) return false;
|
||||
|
||||
uint256 _maxBlockHeightInBatch = blocks[batches[_batchId].batchHash].blockHeight;
|
||||
// check block height is in batch range.
|
||||
if (_maxBlockHeightInBatch == 0) return _blockHeight == 0;
|
||||
else {
|
||||
uint256 _minBlockHeightInBatch = blocks[batches[_batchId].parentHash].blockHeight + 1;
|
||||
return _minBlockHeightInBatch <= _blockHeight && _blockHeight <= _maxBlockHeightInBatch;
|
||||
function verifyMessageStateProof(uint256 _blockNumber) external view override returns (bool) {
|
||||
if (finalizedBlocks[_blockNumber] != bytes32(0)) {
|
||||
return true;
|
||||
}
|
||||
Block storage _block = blocks[lastFinalizedBlockHash];
|
||||
return uint256(_block.header.blockHeight) >= _blockNumber;
|
||||
}
|
||||
|
||||
/**************************************** Mutated Functions ****************************************/
|
||||
@@ -151,113 +133,73 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
|
||||
}
|
||||
|
||||
/// @notice Import layer 2 genesis block
|
||||
function importGenesisBlock(Layer2BlockHeader memory _genesis) external onlyOwner {
|
||||
require(lastFinalizedBatchID == bytes32(0), "Genesis block imported");
|
||||
require(_genesis.blockHash != bytes32(0), "Block hash is zero");
|
||||
require(_genesis.blockHeight == 0, "Block is not genesis");
|
||||
require(_genesis.parentHash == bytes32(0), "Parent hash not empty");
|
||||
function importGenesisBlock(BlockHeader memory _genesis) external onlyOwner {
|
||||
require(lastFinalizedBlockHash == bytes32(0), "genesis block imported");
|
||||
require(_genesis.blockHash != bytes32(0), "invalid block hash");
|
||||
require(_genesis.blockHeight == 0, "not genesis block");
|
||||
require(_genesis.parentHash == bytes32(0), "parent hash not empty");
|
||||
|
||||
require(_verifyBlockHash(_genesis), "Block hash verification failed");
|
||||
Block storage _block = blocks[_genesis.blockHash];
|
||||
_block.header = _genesis;
|
||||
_block.verified = true; // force commited
|
||||
|
||||
Layer2BlockStored storage _block = blocks[_genesis.blockHash];
|
||||
_block.transactionRoot = _computeTransactionRoot(_genesis.txs);
|
||||
lastFinalizedBlockHash = _genesis.blockHash;
|
||||
finalizedBlocks[0] = _genesis.blockHash;
|
||||
|
||||
bytes32 _batchId = _computeBatchId(_genesis.blockHash, bytes32(0), 0);
|
||||
Layer2BatchStored storage _batch = batches[_batchId];
|
||||
|
||||
_batch.batchHash = _genesis.blockHash;
|
||||
_batch.verified = true;
|
||||
|
||||
lastFinalizedBatchID = _batchId;
|
||||
finalizedBatches[0] = _batchId;
|
||||
|
||||
emit CommitBatch(_batchId, _genesis.blockHash, 0, bytes32(0));
|
||||
emit FinalizeBatch(_batchId, _genesis.blockHash, 0, bytes32(0));
|
||||
emit CommitBlock(_genesis.blockHash, 0, bytes32(0));
|
||||
}
|
||||
|
||||
/// @inheritdoc IZKRollup
|
||||
function commitBatch(Layer2Batch memory _batch) external override OnlyOperator {
|
||||
// check whether the batch is empty
|
||||
require(_batch.blocks.length > 0, "Batch is empty");
|
||||
function commitBlock(BlockHeader memory _header, Layer2Transaction[] memory _txn) external override OnlyOperator {
|
||||
Block storage _block = blocks[_header.blockHash];
|
||||
require(_block.header.blockHash == bytes32(0), "Block has been committed before");
|
||||
require(blocks[_header.parentHash].header.blockHash != bytes32(0), "Parent hasn't been committed");
|
||||
|
||||
bytes32 _batchHash = _batch.blocks[_batch.blocks.length - 1].blockHash;
|
||||
bytes32 _batchId = _computeBatchId(_batchHash, _batch.parentHash, _batch.batchIndex);
|
||||
Layer2BatchStored storage _batchStored = batches[_batchId];
|
||||
uint256 _parentHeight = blocks[_header.parentHash].header.blockHeight;
|
||||
// solhint-disable-next-line reason-string
|
||||
require(_parentHeight + 1 == _header.blockHeight, "Block height and parent block height mismatch");
|
||||
|
||||
// check whether the batch is commited before
|
||||
require(_batchStored.batchHash == bytes32(0), "Batch has been committed before");
|
||||
|
||||
// make sure the parent batch is commited before
|
||||
Layer2BlockStored storage _parentBlock = blocks[_batch.parentHash];
|
||||
require(_parentBlock.transactionRoot != bytes32(0), "Parent batch hasn't been committed");
|
||||
require(_parentBlock.batchIndex + 1 == _batch.batchIndex, "Batch index and parent batch index mismatch");
|
||||
|
||||
// check whether the blocks are correct.
|
||||
unchecked {
|
||||
uint256 _expectedBlockHeight = _parentBlock.blockHeight + 1;
|
||||
bytes32 _expectedParentHash = _batch.parentHash;
|
||||
for (uint256 i = 0; i < _batch.blocks.length; i++) {
|
||||
Layer2BlockHeader memory _block = _batch.blocks[i];
|
||||
require(_verifyBlockHash(_block), "Block hash verification failed");
|
||||
require(_block.parentHash == _expectedParentHash, "Block parent hash mismatch");
|
||||
require(_block.blockHeight == _expectedBlockHeight, "Block height mismatch");
|
||||
require(blocks[_block.blockHash].transactionRoot == bytes32(0), "Block has been commited before");
|
||||
|
||||
_expectedBlockHeight += 1;
|
||||
_expectedParentHash = _block.blockHash;
|
||||
}
|
||||
bytes32[] memory _hashes = new bytes32[](_txn.length);
|
||||
for (uint256 i = 0; i < _txn.length; i++) {
|
||||
// @todo use rlp
|
||||
_hashes[i] = keccak256(
|
||||
abi.encode(
|
||||
_txn[i].caller,
|
||||
_txn[i].nonce,
|
||||
_txn[i].target,
|
||||
_txn[i].gas,
|
||||
_txn[i].gasPrice,
|
||||
_txn[i].value,
|
||||
_txn[i].data
|
||||
)
|
||||
);
|
||||
}
|
||||
_block.header = _header;
|
||||
_block.transactionRoot = keccak256(abi.encode(_hashes));
|
||||
|
||||
// do block commit
|
||||
for (uint256 i = 0; i < _batch.blocks.length; i++) {
|
||||
Layer2BlockHeader memory _block = _batch.blocks[i];
|
||||
Layer2BlockStored storage _blockStored = blocks[_block.blockHash];
|
||||
_blockStored.parentHash = _block.parentHash;
|
||||
_blockStored.transactionRoot = _computeTransactionRoot(_block.txs);
|
||||
_blockStored.blockHeight = _block.blockHeight;
|
||||
_blockStored.batchIndex = _batch.batchIndex;
|
||||
}
|
||||
|
||||
_batchStored.batchHash = _batchHash;
|
||||
_batchStored.parentHash = _batch.parentHash;
|
||||
_batchStored.batchIndex = _batch.batchIndex;
|
||||
|
||||
emit CommitBatch(_batchId, _batchHash, _batch.batchIndex, _batch.parentHash);
|
||||
emit CommitBlock(_header.blockHash, _header.blockHeight, _header.parentHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IZKRollup
|
||||
function revertBatch(bytes32 _batchId) external override OnlyOperator {
|
||||
Layer2BatchStored storage _batch = batches[_batchId];
|
||||
function revertBlock(bytes32 _blockHash) external override OnlyOperator {
|
||||
Block storage _block = blocks[_blockHash];
|
||||
require(_block.header.blockHash != bytes32(0), "No such block");
|
||||
require(!_block.verified, "Unable to revert verified block");
|
||||
|
||||
require(_batch.batchHash != bytes32(0), "No such batch");
|
||||
require(!_batch.verified, "Unable to revert verified batch");
|
||||
delete blocks[_blockHash];
|
||||
|
||||
bytes32 _blockHash = _batch.batchHash;
|
||||
bytes32 _parentHash = _batch.parentHash;
|
||||
|
||||
// delete commited blocks
|
||||
while (_blockHash != _parentHash) {
|
||||
bytes32 _nextBlockHash = blocks[_blockHash].parentHash;
|
||||
delete blocks[_blockHash];
|
||||
|
||||
_blockHash = _nextBlockHash;
|
||||
}
|
||||
|
||||
// delete commited batch
|
||||
delete batches[_batchId];
|
||||
|
||||
emit RevertBatch(_batchId);
|
||||
emit RevertBlock(_blockHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IZKRollup
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 _batchId,
|
||||
function finalizeBlockWithProof(
|
||||
bytes32 _blockHash,
|
||||
uint256[] memory _proof,
|
||||
uint256[] memory _instances
|
||||
) external override OnlyOperator {
|
||||
Layer2BatchStored storage _batch = batches[_batchId];
|
||||
require(_batch.batchHash != bytes32(0), "No such batch");
|
||||
require(!_batch.verified, "Batch already verified");
|
||||
Block storage _block = blocks[_blockHash];
|
||||
require(_block.header.blockHash != bytes32(0), "No such block");
|
||||
require(!_block.verified, "Block already verified");
|
||||
|
||||
// @note skip parent check for now, since we may not prove blocks in order.
|
||||
// bytes32 _parentHash = _block.header.parentHash;
|
||||
@@ -268,16 +210,14 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
|
||||
// @todo add verification logic
|
||||
RollupVerifier.verify(_proof, _instances);
|
||||
|
||||
uint256 _batchIndex = _batch.batchIndex;
|
||||
finalizedBatches[_batchIndex] = _batchId;
|
||||
_batch.verified = true;
|
||||
|
||||
Layer2BatchStored storage _finalizedBatch = batches[lastFinalizedBatchID];
|
||||
if (_batchIndex > _finalizedBatch.batchIndex) {
|
||||
lastFinalizedBatchID = _batchId;
|
||||
uint256 _height = _block.header.blockHeight; // gas saving
|
||||
_block.verified = true;
|
||||
finalizedBlocks[_height] = _blockHash;
|
||||
Block storage _finalizedBlock = blocks[lastFinalizedBlockHash];
|
||||
if (_height > _finalizedBlock.header.blockHeight) {
|
||||
lastFinalizedBlockHash = _blockHash;
|
||||
}
|
||||
|
||||
emit FinalizeBatch(_batchId, _batch.batchHash, _batchIndex, _batch.parentHash);
|
||||
emit FinalizeBlock(_blockHash, uint64(_height));
|
||||
}
|
||||
|
||||
/**************************************** Restricted Functions ****************************************/
|
||||
@@ -305,49 +245,4 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup {
|
||||
|
||||
emit UpdateMesssenger(_oldMessenger, _newMessenger);
|
||||
}
|
||||
|
||||
/**************************************** Internal Functions ****************************************/
|
||||
|
||||
function _verifyBlockHash(Layer2BlockHeader memory) internal pure returns (bool) {
|
||||
// @todo finish logic after more discussions
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev Internal function to compute a unique batch id for mapping.
|
||||
/// @param _batchHash The hash of the batch.
|
||||
/// @param _parentHash The hash of the batch.
|
||||
/// @param _batchIndex The index of the batch.
|
||||
/// @return Return the computed batch id.
|
||||
function _computeBatchId(
|
||||
bytes32 _batchHash,
|
||||
bytes32 _parentHash,
|
||||
uint256 _batchIndex
|
||||
) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encode(_batchHash, _parentHash, _batchIndex));
|
||||
}
|
||||
|
||||
/// @dev Internal function to compute transaction root.
|
||||
/// @param _txn The list of transactions in the block.
|
||||
/// @return Return the hash of transaction root.
|
||||
function _computeTransactionRoot(Layer2Transaction[] memory _txn) internal pure returns (bytes32) {
|
||||
bytes32[] memory _hashes = new bytes32[](_txn.length);
|
||||
for (uint256 i = 0; i < _txn.length; i++) {
|
||||
// @todo use rlp
|
||||
_hashes[i] = keccak256(
|
||||
abi.encode(
|
||||
_txn[i].caller,
|
||||
_txn[i].nonce,
|
||||
_txn[i].target,
|
||||
_txn[i].gas,
|
||||
_txn[i].gasPrice,
|
||||
_txn[i].value,
|
||||
_txn[i].data,
|
||||
_txn[i].r,
|
||||
_txn[i].s,
|
||||
_txn[i].v
|
||||
)
|
||||
);
|
||||
}
|
||||
return keccak256(abi.encode(_hashes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,6 @@ contract L2ERC721GatewayTest is DSTestPlus {
|
||||
) public {
|
||||
if (to == address(0)) to = address(1);
|
||||
if (to == address(mockRecipient)) to = address(1);
|
||||
if (to == address(this)) to = address(1);
|
||||
|
||||
gateway.updateTokenMapping(address(token), address(token));
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ contract ZKRollupTest is DSTestPlus {
|
||||
rollup.updateMessenger(_messenger);
|
||||
}
|
||||
|
||||
function testImportGenesisBlock(IZKRollup.Layer2BlockHeader memory _genesis) public {
|
||||
function testImportGenesisBlock(IZKRollup.BlockHeader memory _genesis) public {
|
||||
if (_genesis.blockHash == bytes32(0)) {
|
||||
_genesis.blockHash = bytes32(uint256(1));
|
||||
}
|
||||
@@ -75,142 +75,82 @@ contract ZKRollupTest is DSTestPlus {
|
||||
|
||||
// not genesis block, should revert
|
||||
_genesis.blockHeight = 1;
|
||||
hevm.expectRevert("Block is not genesis");
|
||||
hevm.expectRevert("not genesis block");
|
||||
rollup.importGenesisBlock(_genesis);
|
||||
_genesis.blockHeight = 0;
|
||||
|
||||
// parent hash not empty, should revert
|
||||
_genesis.parentHash = bytes32(uint256(2));
|
||||
hevm.expectRevert("Parent hash not empty");
|
||||
hevm.expectRevert("parent hash not empty");
|
||||
rollup.importGenesisBlock(_genesis);
|
||||
_genesis.parentHash = bytes32(0);
|
||||
|
||||
// invalid block hash, should revert
|
||||
bytes32 _originalHash = _genesis.blockHash;
|
||||
_genesis.blockHash = bytes32(0);
|
||||
hevm.expectRevert("Block hash is zero");
|
||||
hevm.expectRevert("invalid block hash");
|
||||
rollup.importGenesisBlock(_genesis);
|
||||
_genesis.blockHash = _originalHash;
|
||||
|
||||
// TODO: add Block hash verification failed
|
||||
|
||||
// import correctly
|
||||
assertEq(rollup.finalizedBatches(0), bytes32(0));
|
||||
assertEq(rollup.finalizedBlocks(0), bytes32(0));
|
||||
rollup.importGenesisBlock(_genesis);
|
||||
{
|
||||
(bytes32 parentHash, , uint64 blockHeight, uint64 batchIndex) = rollup.blocks(_genesis.blockHash);
|
||||
assertEq(_genesis.parentHash, parentHash);
|
||||
assertEq(_genesis.blockHeight, blockHeight);
|
||||
assertEq(batchIndex, 0);
|
||||
}
|
||||
{
|
||||
bytes32 _batchId = keccak256(abi.encode(_genesis.blockHash, bytes32(0), 0));
|
||||
assertEq(rollup.finalizedBatches(0), _batchId);
|
||||
assertEq(rollup.lastFinalizedBatchID(), _batchId);
|
||||
(bytes32 batchHash, bytes32 parentHash, uint64 batchIndex, bool verified) = rollup.batches(_batchId);
|
||||
assertEq(batchHash, _genesis.blockHash);
|
||||
assertEq(parentHash, bytes32(0));
|
||||
assertEq(batchIndex, 0);
|
||||
assertBoolEq(verified, true);
|
||||
}
|
||||
(IZKRollup.BlockHeader memory _header, , bool _verified) = rollup.blocks(_genesis.blockHash);
|
||||
assertEq(_genesis.blockHash, rollup.lastFinalizedBlockHash());
|
||||
assertEq(_genesis.blockHash, _header.blockHash);
|
||||
assertEq(_genesis.parentHash, _header.parentHash);
|
||||
assertEq(_genesis.baseFee, _header.baseFee);
|
||||
assertEq(_genesis.stateRoot, _header.stateRoot);
|
||||
assertEq(_genesis.blockHeight, _header.blockHeight);
|
||||
assertEq(_genesis.gasUsed, _header.gasUsed);
|
||||
assertEq(_genesis.timestamp, _header.timestamp);
|
||||
assertBytesEq(_genesis.extraData, _header.extraData);
|
||||
assertBoolEq(_verified, true);
|
||||
assertEq(rollup.finalizedBlocks(0), _genesis.blockHash);
|
||||
|
||||
// genesis block imported
|
||||
hevm.expectRevert("Genesis block imported");
|
||||
hevm.expectRevert("genesis block imported");
|
||||
rollup.importGenesisBlock(_genesis);
|
||||
}
|
||||
|
||||
function testCommitBatchFailed() public {
|
||||
function testCommitBlockFailed() public {
|
||||
rollup.updateOperator(address(1));
|
||||
|
||||
IZKRollup.Layer2BlockHeader memory _header;
|
||||
IZKRollup.Layer2Batch memory _batch;
|
||||
IZKRollup.BlockHeader memory _header;
|
||||
IZKRollup.Layer2Transaction[] memory _txns = new IZKRollup.Layer2Transaction[](0);
|
||||
|
||||
// not operator call, should revert
|
||||
hevm.expectRevert("caller not operator");
|
||||
rollup.commitBatch(_batch);
|
||||
rollup.commitBlock(_header, _txns);
|
||||
|
||||
// import fake genesis
|
||||
_header.blockHash = bytes32(uint256(1));
|
||||
rollup.importGenesisBlock(_header);
|
||||
|
||||
hevm.startPrank(address(1));
|
||||
// batch is empty
|
||||
hevm.expectRevert("Batch is empty");
|
||||
rollup.commitBatch(_batch);
|
||||
|
||||
// block submitted, should revert
|
||||
_header.blockHash = bytes32(uint256(1));
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 0;
|
||||
_batch.parentHash = bytes32(0);
|
||||
hevm.expectRevert("Batch has been committed before");
|
||||
rollup.commitBatch(_batch);
|
||||
hevm.expectRevert("Block has been committed before");
|
||||
rollup.commitBlock(_header, _txns);
|
||||
|
||||
// no parent batch, should revert
|
||||
// no parent block, should revert
|
||||
_header.blockHash = bytes32(uint256(2));
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 0;
|
||||
_batch.parentHash = bytes32(0);
|
||||
hevm.expectRevert("Parent batch hasn't been committed");
|
||||
rollup.commitBatch(_batch);
|
||||
hevm.expectRevert("Parent hasn't been committed");
|
||||
rollup.commitBlock(_header, _txns);
|
||||
|
||||
// Batch index and parent batch index mismatch
|
||||
_header.blockHash = bytes32(uint256(2));
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 2;
|
||||
_batch.parentHash = bytes32(uint256(1));
|
||||
hevm.expectRevert("Batch index and parent batch index mismatch");
|
||||
rollup.commitBatch(_batch);
|
||||
|
||||
// BLock parent hash mismatch
|
||||
_header.blockHash = bytes32(uint256(2));
|
||||
_header.parentHash = bytes32(0);
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 1;
|
||||
_batch.parentHash = bytes32(uint256(1));
|
||||
hevm.expectRevert("Block parent hash mismatch");
|
||||
rollup.commitBatch(_batch);
|
||||
|
||||
// Block height mismatch
|
||||
// block height mismatch, should revert
|
||||
_header.blockHash = bytes32(uint256(2));
|
||||
_header.parentHash = bytes32(uint256(1));
|
||||
hevm.expectRevert("Block height and parent block height mismatch");
|
||||
rollup.commitBlock(_header, _txns);
|
||||
|
||||
_header.blockHeight = 2;
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 1;
|
||||
_batch.parentHash = bytes32(uint256(1));
|
||||
hevm.expectRevert("Block height mismatch");
|
||||
rollup.commitBatch(_batch);
|
||||
|
||||
_header.blockHash = bytes32(uint256(2));
|
||||
_header.parentHash = bytes32(uint256(1));
|
||||
_header.blockHeight = 0;
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 1;
|
||||
_batch.parentHash = bytes32(uint256(1));
|
||||
hevm.expectRevert("Block height mismatch");
|
||||
rollup.commitBatch(_batch);
|
||||
|
||||
// Block has been commited before
|
||||
_header.blockHash = bytes32(uint256(1));
|
||||
_header.parentHash = bytes32(uint256(1));
|
||||
_header.blockHeight = 1;
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 1;
|
||||
_batch.parentHash = bytes32(uint256(1));
|
||||
hevm.expectRevert("Block has been commited before");
|
||||
rollup.commitBatch(_batch);
|
||||
|
||||
hevm.expectRevert("Block height and parent block height mismatch");
|
||||
rollup.commitBlock(_header, _txns);
|
||||
hevm.stopPrank();
|
||||
}
|
||||
|
||||
function testCommitBatch(IZKRollup.Layer2BlockHeader memory _header) public {
|
||||
function testCommitBlock(IZKRollup.BlockHeader memory _header) public {
|
||||
if (_header.parentHash == bytes32(0)) {
|
||||
_header.parentHash = bytes32(uint256(1));
|
||||
}
|
||||
@@ -219,40 +159,30 @@ contract ZKRollupTest is DSTestPlus {
|
||||
}
|
||||
rollup.updateOperator(address(1));
|
||||
|
||||
IZKRollup.Layer2Transaction[] memory _txns = new IZKRollup.Layer2Transaction[](0);
|
||||
|
||||
// import fake genesis
|
||||
IZKRollup.Layer2BlockHeader memory _genesis;
|
||||
IZKRollup.BlockHeader memory _genesis;
|
||||
_genesis.blockHash = _header.parentHash;
|
||||
rollup.importGenesisBlock(_genesis);
|
||||
_header.blockHeight = 1;
|
||||
|
||||
IZKRollup.Layer2Batch memory _batch;
|
||||
_batch.blocks = new IZKRollup.Layer2BlockHeader[](1);
|
||||
_batch.blocks[0] = _header;
|
||||
_batch.batchIndex = 1;
|
||||
_batch.parentHash = _header.parentHash;
|
||||
|
||||
// mock caller as operator
|
||||
assertEq(rollup.finalizedBatches(1), bytes32(0));
|
||||
assertEq(rollup.finalizedBlocks(1), bytes32(0));
|
||||
hevm.startPrank(address(1));
|
||||
rollup.commitBatch(_batch);
|
||||
rollup.commitBlock(_header, _txns);
|
||||
hevm.stopPrank();
|
||||
|
||||
// verify block
|
||||
{
|
||||
(bytes32 parentHash, , uint64 blockHeight, uint64 batchIndex) = rollup.blocks(_header.blockHash);
|
||||
assertEq(parentHash, _header.parentHash);
|
||||
assertEq(blockHeight, _header.blockHeight);
|
||||
assertEq(batchIndex, _batch.batchIndex);
|
||||
}
|
||||
// verify batch
|
||||
{
|
||||
bytes32 _batchId = keccak256(abi.encode(_header.blockHash, _header.parentHash, 1));
|
||||
(bytes32 batchHash, bytes32 parentHash, uint64 batchIndex, bool verified) = rollup.batches(_batchId);
|
||||
assertEq(batchHash, _header.blockHash);
|
||||
assertEq(parentHash, _batch.parentHash);
|
||||
assertEq(batchIndex, _batch.batchIndex);
|
||||
assertBoolEq(verified, false);
|
||||
assertEq(rollup.finalizedBatches(1), bytes32(0));
|
||||
}
|
||||
(IZKRollup.BlockHeader memory _storedHeader, , bool _verified) = rollup.blocks(_header.blockHash);
|
||||
assertEq(_header.blockHash, _storedHeader.blockHash);
|
||||
assertEq(_header.parentHash, _storedHeader.parentHash);
|
||||
assertEq(_header.baseFee, _storedHeader.baseFee);
|
||||
assertEq(_header.stateRoot, _storedHeader.stateRoot);
|
||||
assertEq(_header.blockHeight, _storedHeader.blockHeight);
|
||||
assertEq(_header.gasUsed, _storedHeader.gasUsed);
|
||||
assertEq(_header.timestamp, _storedHeader.timestamp);
|
||||
assertBytesEq(_header.extraData, _storedHeader.extraData);
|
||||
assertBoolEq(_verified, false);
|
||||
assertEq(rollup.finalizedBlocks(1), bytes32(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ clean: ## Empty out the bin folder
|
||||
@rm -rf build/bin
|
||||
|
||||
docker:
|
||||
DOCKER_BUILDKIT=1 docker build -t scrolltech/${IMAGE_NAME}:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/coordinator.Dockerfile
|
||||
docker build -t scrolltech/${IMAGE_NAME}:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/coordinator.Dockerfile
|
||||
|
||||
docker_push:
|
||||
docker push scrolltech/${IMAGE_NAME}:${IMAGE_VERSION}
|
||||
|
||||
@@ -12,13 +12,13 @@ type RollerInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
PublicKey string `json:"public_key"`
|
||||
ActiveSession string `json:"active_session,omitempty"`
|
||||
ActiveSession uint64 `json:"active_session,omitempty"`
|
||||
ActiveSessionStartTime time.Time `json:"active_session_start_time"` // latest proof start time.
|
||||
}
|
||||
|
||||
// SessionInfo records proof create or proof verify failed session.
|
||||
type SessionInfo struct {
|
||||
ID string `json:"id"`
|
||||
ID uint64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
FinishTime time.Time `json:"finish_time,omitempty"` // set to 0 if not finished
|
||||
@@ -31,7 +31,7 @@ type RollerDebugAPI interface {
|
||||
// ListRollers returns all live rollers
|
||||
ListRollers() ([]*RollerInfo, error)
|
||||
// GetSessionInfo returns the session information given the session id.
|
||||
GetSessionInfo(sessionID string) (*SessionInfo, error)
|
||||
GetSessionInfo(sessionID uint64) (*SessionInfo, error)
|
||||
}
|
||||
|
||||
// ListRollers returns all live rollers.
|
||||
@@ -47,7 +47,7 @@ func (m *Manager) ListRollers() ([]*RollerInfo, error) {
|
||||
PublicKey: pk,
|
||||
}
|
||||
for id, sess := range m.sessions {
|
||||
if _, ok := sess.rollers[pk]; ok {
|
||||
if sess.rollers[pk] {
|
||||
info.ActiveSessionStartTime = sess.startTime
|
||||
info.ActiveSession = id
|
||||
break
|
||||
@@ -58,13 +58,13 @@ func (m *Manager) ListRollers() ([]*RollerInfo, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func newSessionInfo(s *session, status orm.ProvingStatus, errMsg string, finished bool) *SessionInfo {
|
||||
func newSessionInfo(s *session, status orm.BlockStatus, errMsg string, finished bool) *SessionInfo {
|
||||
now := time.Now()
|
||||
var nameList []string
|
||||
for pk := range s.roller_names {
|
||||
nameList = append(nameList, s.roller_names[pk])
|
||||
}
|
||||
info := &SessionInfo{
|
||||
info := SessionInfo{
|
||||
ID: s.id,
|
||||
Status: status.String(),
|
||||
AssignedRollers: nameList,
|
||||
@@ -74,18 +74,18 @@ func newSessionInfo(s *session, status orm.ProvingStatus, errMsg string, finishe
|
||||
if finished {
|
||||
info.FinishTime = now
|
||||
}
|
||||
return info
|
||||
return &info
|
||||
}
|
||||
|
||||
// GetSessionInfo returns the session information given the session id.
|
||||
func (m *Manager) GetSessionInfo(sessionID string) (*SessionInfo, error) {
|
||||
func (m *Manager) GetSessionInfo(sessionID uint64) (*SessionInfo, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if info, ok := m.failedSessionInfos[sessionID]; ok {
|
||||
return info, nil
|
||||
return &info, nil
|
||||
}
|
||||
if s, ok := m.sessions[sessionID]; ok {
|
||||
return newSessionInfo(s, orm.ProvingTaskAssigned, "", false), nil
|
||||
return newSessionInfo(&s, orm.BlockAssigned, "", false), nil
|
||||
}
|
||||
return nil, fmt.Errorf("no such session, sessionID: %s", sessionID)
|
||||
return nil, fmt.Errorf("no such session, sessionID: %d", sessionID)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,41 @@ package main
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var (
|
||||
commonFlags = []cli.Flag{
|
||||
&configFileFlag,
|
||||
&verbosityFlag,
|
||||
&logFileFlag,
|
||||
&logJSONFormat,
|
||||
&logDebugFlag,
|
||||
&verifierFlag,
|
||||
}
|
||||
// configFileFlag load json type config file.
|
||||
configFileFlag = cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "JSON configuration file",
|
||||
Value: "./config.json",
|
||||
}
|
||||
// verbosityFlag log level.
|
||||
verbosityFlag = cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail",
|
||||
Value: 3,
|
||||
}
|
||||
// logFileFlag decides where the logger output is sent. If this flag is left
|
||||
// empty, it will log to stdout.
|
||||
logFileFlag = cli.StringFlag{
|
||||
Name: "log.file",
|
||||
Usage: "Tells the sequencer where to write log entries",
|
||||
}
|
||||
logJSONFormat = cli.BoolFlag{
|
||||
Name: "log.json",
|
||||
Usage: "Tells the sequencer whether log format is json or not",
|
||||
Value: true,
|
||||
}
|
||||
logDebugFlag = cli.BoolFlag{
|
||||
Name: "log.debug",
|
||||
Usage: "Prepends log messages with call-site location (file and line number)",
|
||||
}
|
||||
verifierFlag = cli.StringFlag{
|
||||
Name: "verifier-socket-file",
|
||||
Usage: "The path of ipc-verifier socket file",
|
||||
|
||||
@@ -11,10 +11,9 @@ import (
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
"scroll-tech/common/version"
|
||||
|
||||
"scroll-tech/database"
|
||||
|
||||
"scroll-tech/coordinator"
|
||||
rollers "scroll-tech/coordinator"
|
||||
"scroll-tech/coordinator/config"
|
||||
)
|
||||
|
||||
@@ -26,16 +25,15 @@ func main() {
|
||||
app.Name = "coordinator"
|
||||
app.Usage = "The Scroll L2 Coordinator"
|
||||
app.Version = version.Version
|
||||
app.Flags = append(app.Flags, utils.CommonFlags...)
|
||||
app.Flags = append(app.Flags, []cli.Flag{&verifierFlag}...)
|
||||
app.Flags = append(app.Flags, commonFlags...)
|
||||
app.Flags = append(app.Flags, apiFlags...)
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
return utils.Setup(&utils.LogConfig{
|
||||
LogFile: ctx.String(utils.LogFileFlag.Name),
|
||||
LogJSONFormat: ctx.Bool(utils.LogJSONFormat.Name),
|
||||
LogDebug: ctx.Bool(utils.LogDebugFlag.Name),
|
||||
Verbosity: ctx.Int(utils.VerbosityFlag.Name),
|
||||
LogFile: ctx.String(logFileFlag.Name),
|
||||
LogJSONFormat: ctx.Bool(logJSONFormat.Name),
|
||||
LogDebug: ctx.Bool(logDebugFlag.Name),
|
||||
Verbosity: ctx.Int(verbosityFlag.Name),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -57,7 +55,7 @@ func applyConfig(ctx *cli.Context, cfg *config.Config) {
|
||||
|
||||
func action(ctx *cli.Context) error {
|
||||
// Load config file.
|
||||
cfgFile := ctx.String(utils.ConfigFileFlag.Name)
|
||||
cfgFile := ctx.String(configFileFlag.Name)
|
||||
cfg, err := config.NewConfig(cfgFile)
|
||||
if err != nil {
|
||||
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
|
||||
@@ -71,7 +69,7 @@ func action(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
// Initialize all coordinator modules.
|
||||
rollerManager, err := coordinator.New(ctx.Context, cfg.RollerManagerConfig, ormFactory)
|
||||
rollerManager, err := rollers.New(ctx.Context, cfg.RollerManagerConfig, ormFactory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
|
||||
db_config "scroll-tech/database"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@ module scroll-tech/coordinator
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/ethereum/go-ethereum v1.10.13
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
)
|
||||
@@ -16,12 +15,12 @@ require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.13 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/iden3/go-iden3-crypto v0.0.12 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/scroll-tech/zktrie v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
|
||||
@@ -318,10 +318,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6 h1:o0Gq2d8nus6x82apluA5RJwjlOca7LIlpAfxlyvQvxs=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6/go.mod h1:jurIpDQ0hqtp9//xxeWzr8X9KMP/+TYn+vz3K1wZrv0=
|
||||
github.com/scroll-tech/zktrie v0.3.0 h1:c0GRNELUyAtyuiwllQKT1XjNbs7NRGfguKouiyLfFNY=
|
||||
github.com/scroll-tech/zktrie v0.3.0/go.mod h1:CuJFlG1/soTJJBAySxCZgTF7oPvd5qF6utHOEciC43Q=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d h1:eh1i1M9BKPCQckNFQsV/yfazQ895hevkWr8GuqhKNrk=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d/go.mod h1:SkQ1431r0LkrExTELsapw6JQHYpki8O1mSsObTSmBWU=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
|
||||
"scroll-tech/common/message"
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/orm"
|
||||
|
||||
"scroll-tech/coordinator/config"
|
||||
@@ -26,32 +26,19 @@ const (
|
||||
proofAndPkBufferSize = 10
|
||||
)
|
||||
|
||||
type rollerStatus int32
|
||||
|
||||
const (
|
||||
rollerAssigned rollerStatus = iota
|
||||
rollerProofValid
|
||||
rollerProofInvalid
|
||||
)
|
||||
|
||||
type rollerProofStatus struct {
|
||||
pk string
|
||||
status rollerStatus
|
||||
}
|
||||
|
||||
// Contains all the information on an ongoing proof generation session.
|
||||
type session struct {
|
||||
// session id
|
||||
id string
|
||||
id uint64
|
||||
// A list of all participating rollers and if they finished proof generation for this session.
|
||||
// The map key is a hexadecimal encoding of the roller public key, as byte slices
|
||||
// can not be compared explicitly.
|
||||
rollers map[string]rollerStatus
|
||||
rollers map[string]bool
|
||||
roller_names map[string]string
|
||||
// session start time
|
||||
startTime time.Time
|
||||
// finish channel is used to pass the public key of the rollers who finished proving process.
|
||||
finishChan chan rollerProofStatus
|
||||
finishChan chan string
|
||||
}
|
||||
|
||||
// Manager is responsible for maintaining connections with active rollers,
|
||||
@@ -74,21 +61,21 @@ type Manager struct {
|
||||
// A mutex guarding the boolean below.
|
||||
mu sync.RWMutex
|
||||
// A map containing all active proof generation sessions.
|
||||
sessions map[string]*session
|
||||
sessions map[uint64]session
|
||||
// A map containing proof failed or verify failed proof.
|
||||
failedSessionInfos map[string]*SessionInfo
|
||||
failedSessionInfos map[uint64]SessionInfo
|
||||
|
||||
// A direct connection to the Halo2 verifier, used to verify
|
||||
// incoming proofs.
|
||||
verifier *verifier.Verifier
|
||||
|
||||
// db interface
|
||||
orm database.OrmFactory
|
||||
orm orm.BlockResultOrm
|
||||
}
|
||||
|
||||
// New returns a new instance of Manager. The instance will be not fully prepared,
|
||||
// and still needs to be finalized and ran by calling `manager.Start`.
|
||||
func New(ctx context.Context, cfg *config.RollerManagerConfig, orm database.OrmFactory) (*Manager, error) {
|
||||
func New(ctx context.Context, cfg *config.RollerManagerConfig, orm orm.BlockResultOrm) (*Manager, error) {
|
||||
var v *verifier.Verifier
|
||||
if cfg.VerifierEndpoint != "" {
|
||||
var err error
|
||||
@@ -104,8 +91,8 @@ func New(ctx context.Context, cfg *config.RollerManagerConfig, orm database.OrmF
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
server: newServer(cfg.Endpoint),
|
||||
sessions: make(map[string]*session),
|
||||
failedSessionInfos: make(map[string]*SessionInfo),
|
||||
sessions: make(map[uint64]session),
|
||||
failedSessionInfos: make(map[uint64]SessionInfo),
|
||||
verifier: v,
|
||||
orm: orm,
|
||||
}, nil
|
||||
@@ -120,8 +107,15 @@ func (m *Manager) Start() error {
|
||||
// m.orm may be nil in scroll tests
|
||||
if m.orm != nil {
|
||||
// clean up assigned but not submitted task
|
||||
if err := m.orm.ResetProvingStatusFor(orm.ProvingTaskAssigned); err != nil {
|
||||
log.Error("fail to reset assigned tasks as unassigned")
|
||||
blocks, err := m.orm.GetBlockResults(map[string]interface{}{"status": orm.BlockAssigned})
|
||||
if err == nil {
|
||||
for _, block := range blocks {
|
||||
if err := m.orm.UpdateBlockStatus(block.BlockTrace.Number.ToInt().Uint64(), orm.BlockUnassigned); err != nil {
|
||||
log.Error("fail to reset block_status as Unassigned")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Error("fail to fetch assigned blocks")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,33 +151,33 @@ func (m *Manager) isRunning() bool {
|
||||
// Loop keeps the manager running.
|
||||
func (m *Manager) Loop() {
|
||||
var (
|
||||
tick = time.NewTicker(time.Second * 3)
|
||||
tasks []*orm.BlockBatch
|
||||
tick = time.NewTicker(time.Second * 3)
|
||||
traces []*types.BlockResult
|
||||
)
|
||||
defer tick.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
if len(tasks) == 0 && m.orm != nil {
|
||||
if len(traces) == 0 && m.orm != nil {
|
||||
var err error
|
||||
numIdleRollers := m.GetNumberOfIdleRollers()
|
||||
// TODO: add cache
|
||||
if tasks, err = m.orm.GetBlockBatches(
|
||||
map[string]interface{}{"proving_status": orm.ProvingTaskUnassigned},
|
||||
if traces, err = m.orm.GetBlockResults(
|
||||
map[string]interface{}{"status": orm.BlockUnassigned},
|
||||
fmt.Sprintf(
|
||||
"ORDER BY index %s LIMIT %d;",
|
||||
"ORDER BY number %s LIMIT %d;",
|
||||
m.cfg.OrderSession,
|
||||
numIdleRollers,
|
||||
),
|
||||
); err != nil {
|
||||
log.Error("failed to get unassigned proving tasks", "error", err)
|
||||
log.Error("failed to get blockResult", "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Select roller and send message
|
||||
for len(tasks) > 0 && m.StartProofGenerationSession(tasks[0]) {
|
||||
tasks = tasks[1:]
|
||||
for len(traces) > 0 && m.StartProofGenerationSession(traces[0]) {
|
||||
traces = traces[1:]
|
||||
}
|
||||
case msg := <-m.server.msgChan:
|
||||
if err := m.HandleMessage(msg.pk, msg.message); err != nil {
|
||||
@@ -213,19 +207,19 @@ func (m *Manager) HandleMessage(pk string, payload []byte) error {
|
||||
}
|
||||
|
||||
switch msg.Type {
|
||||
case message.ErrorMsgType:
|
||||
case message.Error:
|
||||
// Just log it for now.
|
||||
log.Error("error message received from roller", "message", msg)
|
||||
// TODO: handle in m.failedSessionInfos
|
||||
return nil
|
||||
case message.RegisterMsgType:
|
||||
case message.Register:
|
||||
// We shouldn't get this message, as the sequencer should handle registering at the start
|
||||
// of the connection.
|
||||
return errors.New("attempted handshake at the wrong time")
|
||||
case message.TaskMsgType:
|
||||
case message.BlockTrace:
|
||||
// We shouldn't get this message, as the sequencer should always be the one to send it
|
||||
return errors.New("received illegal message")
|
||||
case message.ProofMsgType:
|
||||
case message.Proof:
|
||||
return m.HandleZkProof(pk, msg.Payload)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized message type %v", msg.Type)
|
||||
@@ -237,7 +231,6 @@ func (m *Manager) HandleMessage(pk string, payload []byte) error {
|
||||
// db/unmarshal errors will not because they are errors on the business logic side.
|
||||
func (m *Manager) HandleZkProof(pk string, payload []byte) error {
|
||||
var dbErr error
|
||||
var success bool
|
||||
|
||||
msg := &message.ProofMsg{}
|
||||
if err := json.Unmarshal(payload, msg); err != nil {
|
||||
@@ -256,71 +249,57 @@ func (m *Manager) HandleZkProof(pk string, payload []byte) error {
|
||||
proofTimeSec := uint64(time.Since(s.startTime).Seconds())
|
||||
|
||||
// Ensure this roller is eligible to participate in the session.
|
||||
if status, ok := s.rollers[pk]; !ok {
|
||||
if _, ok = s.rollers[pk]; !ok {
|
||||
return fmt.Errorf("roller %s is not eligible to partake in proof session %v", pk, msg.ID)
|
||||
} else if status == rollerProofValid {
|
||||
// In order to prevent DoS attacks, it is forbidden to repeatedly submit valid proofs.
|
||||
// TODO: Defend invalid proof resubmissions by one of the following two methods:
|
||||
// (i) slash the roller for each submission of invalid proof
|
||||
// (ii) set the maximum failure retry times
|
||||
log.Warn("roller has already submitted valid proof in proof session", "roller", pk, "proof id", msg.ID)
|
||||
return nil
|
||||
}
|
||||
log.Info("Received zk proof", "proof id", msg.ID)
|
||||
|
||||
defer func() {
|
||||
// notify the session that the roller finishes the proving process
|
||||
s.finishChan <- pk
|
||||
// TODO: maybe we should use db tx for the whole process?
|
||||
// Roll back current proof's status.
|
||||
if dbErr != nil {
|
||||
if err := m.orm.UpdateProvingStatus(msg.ID, orm.ProvingTaskUnassigned); err != nil {
|
||||
log.Error("fail to reset task status as Unassigned", "msg.ID", msg.ID)
|
||||
if err := m.orm.UpdateBlockStatus(msg.ID, orm.BlockUnassigned); err != nil {
|
||||
log.Error("fail to reset block_status as Unassigned", "msg.ID", msg.ID)
|
||||
}
|
||||
}
|
||||
// set proof status
|
||||
var status rollerStatus
|
||||
if success && dbErr == nil {
|
||||
status = rollerProofValid
|
||||
} else {
|
||||
status = rollerProofInvalid
|
||||
}
|
||||
// notify the session that the roller finishes the proving process
|
||||
s.finishChan <- rollerProofStatus{pk, status}
|
||||
}()
|
||||
|
||||
if msg.Status != message.StatusOk {
|
||||
log.Error("Roller failed to generate proof", "msg.ID", msg.ID, "error", msg.Error)
|
||||
if dbErr = m.orm.UpdateProvingStatus(msg.ID, orm.ProvingTaskFailed); dbErr != nil {
|
||||
log.Error("failed to update task status as failed", "error", dbErr)
|
||||
if dbErr = m.orm.UpdateBlockStatus(msg.ID, orm.BlockFailed); dbErr != nil {
|
||||
log.Error("failed to update blockResult status", "status", orm.BlockFailed, "error", dbErr)
|
||||
}
|
||||
// record the failed session.
|
||||
m.addFailedSession(s, msg.Error)
|
||||
m.addFailedSession(&s, msg.Error)
|
||||
return nil
|
||||
}
|
||||
|
||||
// store proof content
|
||||
if dbErr = m.orm.UpdateProofByID(m.ctx, msg.ID, msg.Proof.Proof, msg.Proof.FinalPair, proofTimeSec); dbErr != nil {
|
||||
if dbErr = m.orm.UpdateProofByNumber(m.ctx, msg.ID, msg.Proof.Proof, msg.Proof.FinalPair, proofTimeSec); dbErr != nil {
|
||||
log.Error("failed to store proof into db", "error", dbErr)
|
||||
return dbErr
|
||||
}
|
||||
if dbErr = m.orm.UpdateProvingStatus(msg.ID, orm.ProvingTaskProved); dbErr != nil {
|
||||
log.Error("failed to update task status as proved", "error", dbErr)
|
||||
if dbErr = m.orm.UpdateBlockStatus(msg.ID, orm.BlockProved); dbErr != nil {
|
||||
log.Error("failed to update blockResult status", "status", orm.BlockProved, "error", dbErr)
|
||||
return dbErr
|
||||
}
|
||||
|
||||
var success bool
|
||||
if m.verifier != nil {
|
||||
var err error
|
||||
tasks, err := m.orm.GetBlockBatches(map[string]interface{}{"id": msg.ID})
|
||||
if len(tasks) == 0 {
|
||||
blockResults, err := m.orm.GetBlockResults(map[string]interface{}{"number": msg.ID})
|
||||
if len(blockResults) == 0 {
|
||||
if err != nil {
|
||||
log.Error("failed to get tasks", "error", err)
|
||||
log.Error("failed to get blockResults", "error", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
success, err = m.verifier.VerifyProof(msg.Proof)
|
||||
success, err = m.verifier.VerifyProof(blockResults[0], msg.Proof)
|
||||
if err != nil {
|
||||
// record failed session.
|
||||
m.addFailedSession(s, err.Error())
|
||||
m.addFailedSession(&s, err.Error())
|
||||
// TODO: this is only a temp workaround for testnet, we should return err in real cases
|
||||
success = false
|
||||
log.Error("Failed to verify zk proof", "proof id", msg.ID, "error", err)
|
||||
@@ -334,25 +313,25 @@ func (m *Manager) HandleZkProof(pk string, payload []byte) error {
|
||||
log.Info("Verify zk proof successfully", "verification result", success, "proof id", msg.ID)
|
||||
}
|
||||
|
||||
var status orm.ProvingStatus
|
||||
var status orm.BlockStatus
|
||||
if success {
|
||||
status = orm.ProvingTaskVerified
|
||||
status = orm.BlockVerified
|
||||
} else {
|
||||
// Set status as skipped if verification fails.
|
||||
// Note that this is only a workaround for testnet here.
|
||||
// TODO: In real cases we should reset to orm.ProvingTaskUnassigned
|
||||
// TODO: In real cases we should reset to orm.BlockUnassigned
|
||||
// so as to re-distribute the task in the future
|
||||
status = orm.ProvingTaskFailed
|
||||
status = orm.BlockFailed
|
||||
}
|
||||
if dbErr = m.orm.UpdateProvingStatus(msg.ID, status); dbErr != nil {
|
||||
log.Error("failed to update proving_status", "msg.ID", msg.ID, "status", status, "error", dbErr)
|
||||
if dbErr = m.orm.UpdateBlockStatus(msg.ID, status); dbErr != nil {
|
||||
log.Error("failed to update blockResult status", "status", status, "error", dbErr)
|
||||
}
|
||||
|
||||
return dbErr
|
||||
}
|
||||
|
||||
// CollectProofs collects proofs corresponding to a proof generation session.
|
||||
func (m *Manager) CollectProofs(id string, s *session) {
|
||||
func (m *Manager) CollectProofs(id uint64, s session) {
|
||||
timer := time.NewTimer(time.Duration(m.cfg.CollectionTime) * time.Minute)
|
||||
|
||||
for {
|
||||
@@ -367,25 +346,25 @@ func (m *Manager) CollectProofs(id string, s *session) {
|
||||
}()
|
||||
|
||||
// Pick a random winner.
|
||||
// First, round up the keys that actually sent in a valid proof.
|
||||
// First, round up the keys that actually sent in a proof.
|
||||
var participatingRollers []string
|
||||
for pk, status := range s.rollers {
|
||||
if status == rollerProofValid {
|
||||
for pk, finished := range s.rollers {
|
||||
if finished {
|
||||
participatingRollers = append(participatingRollers, pk)
|
||||
}
|
||||
}
|
||||
// Ensure we got at least one proof before selecting a winner.
|
||||
if len(participatingRollers) == 0 {
|
||||
// record failed session.
|
||||
errMsg := "proof generation session ended without receiving any valid proofs"
|
||||
m.addFailedSession(s, errMsg)
|
||||
errMsg := "proof generation session ended without receiving any proofs"
|
||||
m.addFailedSession(&s, errMsg)
|
||||
log.Warn(errMsg, "session id", id)
|
||||
// Set status as skipped.
|
||||
// Note that this is only a workaround for testnet here.
|
||||
// TODO: In real cases we should reset to orm.ProvingTaskUnassigned
|
||||
// TODO: In real cases we should reset to orm.BlockUnassigned
|
||||
// so as to re-distribute the task in the future
|
||||
if err := m.orm.UpdateProvingStatus(id, orm.ProvingTaskFailed); err != nil {
|
||||
log.Error("fail to reset task_status as Unassigned", "id", id, "err", err)
|
||||
if err := m.orm.UpdateBlockStatus(id, orm.BlockFailed); err != nil {
|
||||
log.Error("fail to reset block_status as Unassigned", "id", id)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -395,9 +374,9 @@ func (m *Manager) CollectProofs(id string, s *session) {
|
||||
_ = participatingRollers[randIndex]
|
||||
// TODO: reward winner
|
||||
return
|
||||
case ret := <-s.finishChan:
|
||||
case pk := <-s.finishChan:
|
||||
m.mu.Lock()
|
||||
s.rollers[ret.pk] = ret.status
|
||||
s.rollers[pk] = true
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -420,20 +399,20 @@ func (m *Manager) APIs() []rpc.API {
|
||||
}
|
||||
|
||||
// StartProofGenerationSession starts a proof generation session
|
||||
func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) bool {
|
||||
func (m *Manager) StartProofGenerationSession(trace *types.BlockResult) bool {
|
||||
roller := m.SelectRoller()
|
||||
if roller == nil || roller.isClosed() {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("start proof generation session", "id", task.ID)
|
||||
id := (*big.Int)(trace.BlockTrace.Number).Uint64()
|
||||
log.Info("start proof generation session", "id", id)
|
||||
|
||||
var dbErr error
|
||||
defer func() {
|
||||
if dbErr != nil {
|
||||
log.Error("StartProofGenerationSession", "dbErr", dbErr)
|
||||
if err := m.orm.UpdateProvingStatus(task.ID, orm.ProvingTaskUnassigned); err != nil {
|
||||
log.Error("fail to reset task_status as Unassigned", "id", task.ID, "dbErr", dbErr, "err", err)
|
||||
if err := m.orm.UpdateBlockStatus(id, orm.BlockUnassigned); err != nil {
|
||||
log.Error("fail to reset block_status as Unassigned", "id", id)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -441,17 +420,7 @@ func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) bool {
|
||||
pk := roller.AuthMsg.Identity.PublicKey
|
||||
log.Info("roller is picked", "name", roller.AuthMsg.Identity.Name, "public_key", pk)
|
||||
|
||||
traces, err := m.orm.GetBlockTraces(map[string]interface{}{"batch_id": task.ID})
|
||||
if err != nil {
|
||||
log.Error(
|
||||
"could not GetBlockTraces",
|
||||
"batch_id", task.ID,
|
||||
"error", err,
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
msg, err := createTaskMsg(task.ID, traces)
|
||||
msg, err := createBlockTracesMsg(trace)
|
||||
if err != nil {
|
||||
log.Error(
|
||||
"could not create block traces message",
|
||||
@@ -459,7 +428,6 @@ func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) bool {
|
||||
)
|
||||
return false
|
||||
}
|
||||
// TODO: use some compression?
|
||||
if err := roller.sendMessage(msg); err != nil {
|
||||
log.Error(
|
||||
"could not send traces message to roller",
|
||||
@@ -468,25 +436,25 @@ func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
s := &session{
|
||||
id: task.ID,
|
||||
rollers: map[string]rollerStatus{
|
||||
pk: rollerAssigned,
|
||||
s := session{
|
||||
id: id,
|
||||
rollers: map[string]bool{
|
||||
pk: false,
|
||||
},
|
||||
roller_names: map[string]string{
|
||||
pk: roller.AuthMsg.Identity.Name,
|
||||
},
|
||||
startTime: time.Now(),
|
||||
finishChan: make(chan rollerProofStatus, proofAndPkBufferSize),
|
||||
finishChan: make(chan string, proofAndPkBufferSize),
|
||||
}
|
||||
|
||||
// Create a proof generation session.
|
||||
m.mu.Lock()
|
||||
m.sessions[task.ID] = s
|
||||
m.sessions[id] = s
|
||||
m.mu.Unlock()
|
||||
|
||||
dbErr = m.orm.UpdateProvingStatus(task.ID, orm.ProvingTaskAssigned)
|
||||
go m.CollectProofs(task.ID, s)
|
||||
dbErr = m.orm.UpdateBlockStatus(id, orm.BlockAssigned)
|
||||
go m.CollectProofs(id, s)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -525,8 +493,8 @@ func (m *Manager) IsRollerIdle(hexPk string) bool {
|
||||
// We need to iterate over all sessions because finished sessions will be deleted until the
|
||||
// timeout. So a busy roller could be marked as idle in a finished session.
|
||||
for _, sess := range m.sessions {
|
||||
for pk, status := range sess.rollers {
|
||||
if pk == hexPk && status == rollerAssigned {
|
||||
for pk, finished := range sess.rollers {
|
||||
if pk == hexPk && !finished {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -547,23 +515,23 @@ func (m *Manager) GetNumberOfIdleRollers() int {
|
||||
return cnt
|
||||
}
|
||||
|
||||
func createTaskMsg(taskID string, traces []*types.BlockTrace) (*message.Msg, error) {
|
||||
idAndTraces := message.TaskMsg{
|
||||
ID: taskID,
|
||||
Traces: traces, // roller should sort traces by height
|
||||
func createBlockTracesMsg(traces *types.BlockResult) (message.Msg, error) {
|
||||
idAndTraces := message.BlockTraces{
|
||||
ID: traces.BlockTrace.Number.ToInt().Uint64(),
|
||||
Traces: traces,
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(idAndTraces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return message.Msg{}, err
|
||||
}
|
||||
|
||||
return &message.Msg{
|
||||
Type: message.TaskMsgType,
|
||||
return message.Msg{
|
||||
Type: message.BlockTrace,
|
||||
Payload: payload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) addFailedSession(s *session, errMsg string) {
|
||||
m.failedSessionInfos[s.id] = newSessionInfo(s, orm.ProvingTaskFailed, errMsg, true)
|
||||
m.failedSessionInfos[s.id] = *newSessionInfo(s, orm.BlockFailed, errMsg, true)
|
||||
}
|
||||
|
||||
@@ -2,30 +2,26 @@ package coordinator_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
mathrand "math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
"scroll-tech/common/message"
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/migrate"
|
||||
"scroll-tech/common/utils"
|
||||
db_config "scroll-tech/database"
|
||||
"scroll-tech/database/orm"
|
||||
|
||||
"scroll-tech/bridge/mock"
|
||||
|
||||
"scroll-tech/coordinator"
|
||||
"scroll-tech/coordinator/config"
|
||||
)
|
||||
@@ -34,8 +30,20 @@ const managerAddr = "localhost:8132"
|
||||
const managerPort = ":8132"
|
||||
|
||||
var (
|
||||
dbConfig = &database.DBConfig{
|
||||
DriverName: "postgres",
|
||||
DB_CONFIG = &db_config.DBConfig{
|
||||
DriverName: utils.GetEnvWithDefault("TEST_DB_DRIVER", "postgres"),
|
||||
DSN: utils.GetEnvWithDefault("TEST_DB_DSN", "postgres://postgres:123456@localhost:5436/testmanager_db?sslmode=disable"),
|
||||
}
|
||||
|
||||
TEST_CONFIG = &mock.TestConfig{
|
||||
L2GethTestConfig: mock.L2GethTestConfig{
|
||||
HPort: 8536,
|
||||
WPort: 0,
|
||||
},
|
||||
DbTestconfig: mock.DbTestconfig{
|
||||
DbName: "testmanager_db",
|
||||
DbPort: 5436,
|
||||
},
|
||||
}
|
||||
l2gethImg docker.ImgInstance
|
||||
dbImg docker.ImgInstance
|
||||
@@ -43,10 +51,9 @@ var (
|
||||
|
||||
func setupEnv(t *testing.T) {
|
||||
// initialize l2geth docker image
|
||||
l2gethImg = docker.NewTestL2Docker(t)
|
||||
l2gethImg = mock.NewTestL2Docker(t, TEST_CONFIG)
|
||||
// initialize db docker image
|
||||
dbImg = docker.NewTestDBDocker(t, "postgres")
|
||||
dbConfig.DSN = dbImg.Endpoint()
|
||||
dbImg = mock.GetDbDocker(t, TEST_CONFIG)
|
||||
}
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
@@ -55,7 +62,6 @@ func TestFunction(t *testing.T) {
|
||||
|
||||
t.Run("TestHandshake", func(t *testing.T) {
|
||||
verifierEndpoint := setupMockVerifier(t)
|
||||
defer os.RemoveAll(verifierEndpoint)
|
||||
|
||||
rollerManager := setupRollerManager(t, verifierEndpoint, nil)
|
||||
defer rollerManager.Stop()
|
||||
@@ -66,7 +72,7 @@ func TestFunction(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
performHandshake(t, c)
|
||||
mock.PerformHandshake(t, c)
|
||||
|
||||
// Roller manager should send a Websocket over the GetRollerChan
|
||||
select {
|
||||
@@ -79,7 +85,6 @@ func TestFunction(t *testing.T) {
|
||||
|
||||
t.Run("TestHandshakeTimeout", func(t *testing.T) {
|
||||
verifierEndpoint := setupMockVerifier(t)
|
||||
defer os.RemoveAll(verifierEndpoint)
|
||||
|
||||
rollerManager := setupRollerManager(t, verifierEndpoint, nil)
|
||||
defer rollerManager.Stop()
|
||||
@@ -94,7 +99,7 @@ func TestFunction(t *testing.T) {
|
||||
// Wait for the handshake timeout to pass
|
||||
<-time.After(coordinator.HandshakeTimeout + 1*time.Second)
|
||||
|
||||
performHandshake(t, c)
|
||||
mock.PerformHandshake(t, c)
|
||||
|
||||
// No websocket should be received
|
||||
select {
|
||||
@@ -107,8 +112,6 @@ func TestFunction(t *testing.T) {
|
||||
|
||||
t.Run("TestTwoConnections", func(t *testing.T) {
|
||||
verifierEndpoint := setupMockVerifier(t)
|
||||
defer os.RemoveAll(verifierEndpoint)
|
||||
|
||||
rollerManager := setupRollerManager(t, verifierEndpoint, nil)
|
||||
defer rollerManager.Stop()
|
||||
|
||||
@@ -120,7 +123,7 @@ func TestFunction(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
performHandshake(t, c)
|
||||
mock.PerformHandshake(t, c)
|
||||
|
||||
// Roller manager should send a Websocket over the GetRollerChan
|
||||
select {
|
||||
@@ -133,43 +136,51 @@ func TestFunction(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("TestTriggerProofGenerationSession", func(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(dbConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
// prepare DB
|
||||
mock.ClearDB(t, DB_CONFIG)
|
||||
db := mock.PrepareDB(t, DB_CONFIG)
|
||||
|
||||
// Test with two clients to make sure traces messages aren't duplicated
|
||||
// to rollers.
|
||||
numClients := uint8(2)
|
||||
verifierEndpoint := setupMockVerifier(t)
|
||||
defer os.RemoveAll(verifierEndpoint)
|
||||
|
||||
rollerManager := setupRollerManager(t, verifierEndpoint, db)
|
||||
defer rollerManager.Stop()
|
||||
|
||||
// Set up and register `numClients` clients
|
||||
conns := make([]*websocket.Conn, numClients)
|
||||
for i := 0; i < int(numClients); i++ {
|
||||
u := url.URL{Scheme: "ws", Host: managerAddr, Path: "/"}
|
||||
|
||||
var conn *websocket.Conn
|
||||
conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
assert.NoError(t, err)
|
||||
defer conn.Close()
|
||||
defer c.Close()
|
||||
|
||||
performHandshake(t, conn)
|
||||
mock.PerformHandshake(t, c)
|
||||
|
||||
conns[i] = conn
|
||||
conns[i] = c
|
||||
}
|
||||
|
||||
dbTx, err := db.Beginx()
|
||||
var results []*types.BlockResult
|
||||
|
||||
templateBlockResult, err := os.ReadFile("../common/testdata/blockResult_orm.json")
|
||||
assert.NoError(t, err)
|
||||
_, err = db.NewBatchInDBTx(dbTx, &orm.BlockInfo{Number: uint64(1)}, &orm.BlockInfo{Number: uint64(1)}, "0f", 1, 194676)
|
||||
blockResult := &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
_, err = db.NewBatchInDBTx(dbTx, &orm.BlockInfo{Number: uint64(2)}, &orm.BlockInfo{Number: uint64(2)}, "0e", 1, 194676)
|
||||
results = append(results, blockResult)
|
||||
templateBlockResult, err = os.ReadFile("../common/testdata/blockResult_delegate.json")
|
||||
assert.NoError(t, err)
|
||||
err = dbTx.Commit()
|
||||
blockResult = &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
results = append(results, blockResult)
|
||||
|
||||
err = db.InsertBlockResultsWithStatus(context.Background(), results, orm.BlockUnassigned)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Need to send a tx to trigger block committed
|
||||
// Sleep for a little bit, so that we can avoid prematurely fetching connections. Trigger for manager is 3 seconds
|
||||
time.Sleep(4 * time.Second)
|
||||
|
||||
// Both rollers should now receive a `BlockTraces` message and should send something back.
|
||||
for _, c := range conns {
|
||||
@@ -180,25 +191,24 @@ func TestFunction(t *testing.T) {
|
||||
|
||||
msg := &message.Msg{}
|
||||
assert.NoError(t, json.Unmarshal(payload, msg))
|
||||
assert.Equal(t, msg.Type, message.TaskMsgType)
|
||||
assert.Equal(t, msg.Type, message.BlockTrace)
|
||||
|
||||
traces := &message.TaskMsg{}
|
||||
traces := &message.BlockTraces{}
|
||||
assert.NoError(t, json.Unmarshal(payload, traces))
|
||||
|
||||
}
|
||||
|
||||
rollerManager.Stop()
|
||||
})
|
||||
|
||||
t.Run("TestIdleRollerSelection", func(t *testing.T) {
|
||||
// Create db handler and reset db.
|
||||
db, err := database.NewOrmFactory(dbConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
|
||||
|
||||
// Test with two clients to make sure traces messages aren't duplicated
|
||||
// to rollers.
|
||||
numClients := uint8(2)
|
||||
verifierEndpoint := setupMockVerifier(t)
|
||||
defer os.RemoveAll(verifierEndpoint)
|
||||
|
||||
mock.ClearDB(t, DB_CONFIG)
|
||||
db := mock.PrepareDB(t, DB_CONFIG)
|
||||
|
||||
// Ensure only one roller is picked per session.
|
||||
rollerManager := setupRollerManager(t, verifierEndpoint, db)
|
||||
@@ -209,14 +219,13 @@ func TestFunction(t *testing.T) {
|
||||
for i := 0; i < int(numClients); i++ {
|
||||
u := url.URL{Scheme: "ws", Host: managerAddr, Path: "/"}
|
||||
|
||||
var conn *websocket.Conn
|
||||
conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
assert.NoError(t, err)
|
||||
conn.SetReadDeadline(time.Now().Add(100 * time.Second))
|
||||
c.SetReadDeadline(time.Now().Add(100 * time.Second))
|
||||
|
||||
performHandshake(t, conn)
|
||||
mock.PerformHandshake(t, c)
|
||||
time.Sleep(1 * time.Second)
|
||||
conns[i] = conn
|
||||
conns[i] = c
|
||||
}
|
||||
defer func() {
|
||||
for _, conn := range conns {
|
||||
@@ -226,29 +235,31 @@ func TestFunction(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 2, rollerManager.GetNumberOfIdleRollers())
|
||||
|
||||
dbTx, err := db.Beginx()
|
||||
templateBlockResult, err := os.ReadFile("../common/testdata/blockResult_orm.json")
|
||||
assert.NoError(t, err)
|
||||
_, err = db.NewBatchInDBTx(dbTx, &orm.BlockInfo{Number: uint64(1)}, &orm.BlockInfo{Number: uint64(1)}, "0f", 1, 194676)
|
||||
blockResult := &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
err = dbTx.Commit()
|
||||
err = db.InsertBlockResultsWithStatus(context.Background(), []*types.BlockResult{blockResult}, orm.BlockUnassigned)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Sleep for a little bit, so that we can avoid prematurely fetching connections.
|
||||
// Test Second roller and check if we have all rollers busy
|
||||
time.Sleep(3 * time.Second)
|
||||
// Test first roller and check if we have one roller idle one roller busy
|
||||
time.Sleep(4 * time.Second)
|
||||
|
||||
assert.Equal(t, 1, rollerManager.GetNumberOfIdleRollers())
|
||||
|
||||
dbTx, err = db.Beginx()
|
||||
templateBlockResult, err = os.ReadFile("../common/testdata/blockResult_delegate.json")
|
||||
assert.NoError(t, err)
|
||||
_, err = db.NewBatchInDBTx(dbTx, &orm.BlockInfo{Number: uint64(2)}, &orm.BlockInfo{Number: uint64(2)}, "0e", 1, 194676)
|
||||
blockResult = &types.BlockResult{}
|
||||
err = json.Unmarshal(templateBlockResult, blockResult)
|
||||
assert.NoError(t, err)
|
||||
err = dbTx.Commit()
|
||||
err = db.InsertBlockResultsWithStatus(context.Background(), []*types.BlockResult{blockResult}, orm.BlockUnassigned)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Sleep for a little bit, so that we can avoid prematurely fetching connections.
|
||||
// Test Second roller and check if we have all rollers busy
|
||||
time.Sleep(3 * time.Second)
|
||||
time.Sleep(4 * time.Second)
|
||||
|
||||
for _, c := range conns {
|
||||
c.ReadMessage()
|
||||
@@ -261,9 +272,10 @@ func TestFunction(t *testing.T) {
|
||||
assert.NoError(t, l2gethImg.Stop())
|
||||
assert.NoError(t, dbImg.Stop())
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func setupRollerManager(t *testing.T, verifierEndpoint string, orm database.OrmFactory) *coordinator.Manager {
|
||||
func setupRollerManager(t *testing.T, verifierEndpoint string, orm orm.BlockResultOrm) *coordinator.Manager {
|
||||
rollerManager, err := coordinator.New(context.Background(), &config.RollerManagerConfig{
|
||||
Endpoint: managerPort,
|
||||
RollersPerSession: 1,
|
||||
@@ -277,84 +289,13 @@ func setupRollerManager(t *testing.T, verifierEndpoint string, orm database.OrmF
|
||||
return rollerManager
|
||||
}
|
||||
|
||||
// performHandshake sets up a websocket client to connect to the roller manager.
|
||||
func performHandshake(t *testing.T, c *websocket.Conn) {
|
||||
// Try to perform handshake
|
||||
pk, sk := generateKeyPair()
|
||||
authMsg := &message.AuthMessage{
|
||||
Identity: message.Identity{
|
||||
Name: "testRoller",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
PublicKey: common.Bytes2Hex(pk),
|
||||
},
|
||||
Signature: "",
|
||||
}
|
||||
|
||||
hash, err := authMsg.Identity.Hash()
|
||||
assert.NoError(t, err)
|
||||
sig, err := secp256k1.Sign(hash, sk)
|
||||
assert.NoError(t, err)
|
||||
|
||||
authMsg.Signature = common.Bytes2Hex(sig)
|
||||
|
||||
b, err := json.Marshal(authMsg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := &message.Msg{
|
||||
Type: message.RegisterMsgType,
|
||||
Payload: b,
|
||||
}
|
||||
|
||||
b, err = json.Marshal(msg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, c.WriteMessage(websocket.BinaryMessage, b))
|
||||
}
|
||||
|
||||
func generateKeyPair() (pubkey, privkey []byte) {
|
||||
key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubkey = elliptic.Marshal(secp256k1.S256(), key.X, key.Y)
|
||||
|
||||
privkey = make([]byte, 32)
|
||||
blob := key.D.Bytes()
|
||||
copy(privkey[32-len(blob):], blob)
|
||||
|
||||
return pubkey, privkey
|
||||
}
|
||||
|
||||
// setupMockVerifier sets up a mocked verifier for a test case.
|
||||
func setupMockVerifier(t *testing.T) string {
|
||||
verifierEndpoint := "/tmp/" + strconv.Itoa(time.Now().Nanosecond()) + ".sock"
|
||||
|
||||
// Create and listen sock file.
|
||||
l, err := net.Listen("unix", verifierEndpoint)
|
||||
id := strconv.Itoa(mathrand.Int())
|
||||
verifierEndpoint := "/tmp/" + id + ".sock"
|
||||
err := os.RemoveAll(verifierEndpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
conn, err := l.Accept()
|
||||
assert.NoError(t, err)
|
||||
mock.SetupMockVerifier(t, verifierEndpoint)
|
||||
|
||||
// Simply read all incoming messages and send a true boolean straight back.
|
||||
for {
|
||||
// Read length
|
||||
buf := make([]byte, 4)
|
||||
_, err = io.ReadFull(conn, buf)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Read message
|
||||
msgLength := binary.LittleEndian.Uint64(buf)
|
||||
buf = make([]byte, msgLength)
|
||||
_, err = io.ReadFull(conn, buf)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Return boolean
|
||||
buf = []byte{1}
|
||||
_, err = conn.Write(buf)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
return verifierEndpoint
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ func (s *server) handshake(c *websocket.Conn) (*message.AuthMessage, error) {
|
||||
}
|
||||
|
||||
// We should receive a Register message
|
||||
if msg.Type != message.RegisterMsgType {
|
||||
if msg.Type != message.Register {
|
||||
return nil, errors.New("got wrong handshake message, expected Register")
|
||||
}
|
||||
|
||||
@@ -291,8 +291,8 @@ func (s *server) pingLoop(c *Roller) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Roller) sendMessage(msg *message.Msg) error {
|
||||
b, err := json.Marshal(msg)
|
||||
func (r *Roller) sendMessage(msg message.Msg) error {
|
||||
b, err := json.Marshal(&msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"net"
|
||||
|
||||
"scroll-tech/common/message"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// Verifier represents a socket connection to a halo2 verifier.
|
||||
@@ -29,7 +31,7 @@ func (v *Verifier) Stop() error {
|
||||
}
|
||||
|
||||
// VerifyProof Verify a ZkProof by marshaling it and sending it to the Halo2 Verifier.
|
||||
func (v *Verifier) VerifyProof(proof *message.AggProof) (bool, error) {
|
||||
func (v *Verifier) VerifyProof(blockResult *types.BlockResult, proof *message.AggProof) (bool, error) {
|
||||
buf, err := proof.Marshal()
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -34,9 +34,9 @@ func TestIPCComm(t *testing.T) {
|
||||
StateProof: stateProof,
|
||||
}
|
||||
|
||||
traces := &types.BlockTrace{}
|
||||
traces := &types.BlockResult{}
|
||||
|
||||
verified, err := verifier.VerifyProof(proof)
|
||||
verified, err := verifier.VerifyProof(traces, proof)
|
||||
assert.NoError(err)
|
||||
assert.True(verified)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
.PHONY: lint
|
||||
|
||||
IMAGE_NAME=db_cli
|
||||
IMAGE_VERSION=latest
|
||||
REPO_ROOT_DIR=./..
|
||||
|
||||
db_cli:
|
||||
go build -o $(PWD)/build/bin/db_cli ./cmd
|
||||
|
||||
test:
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 $(PWD)/...
|
||||
|
||||
lint: ## Lint the files - used for CI
|
||||
GOBIN=$(PWD)/build/bin go run ../build/lint.go
|
||||
|
||||
docker:
|
||||
DOCKER_BUILDKIT=1 docker build -t scrolltech/${IMAGE_NAME}:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/${IMAGE_NAME}.Dockerfile
|
||||
|
||||
docker_push:
|
||||
docker push scrolltech/${IMAGE_NAME}:${IMAGE_VERSION}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var (
|
||||
commonFlags = []cli.Flag{
|
||||
&configFileFlag,
|
||||
&verbosityFlag,
|
||||
&logFileFlag,
|
||||
&logJSONFormat,
|
||||
&logDebugFlag,
|
||||
}
|
||||
// configFileFlag load json type config file.
|
||||
configFileFlag = cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "JSON configuration file",
|
||||
Value: "./config.json",
|
||||
}
|
||||
// verbosityFlag log level.
|
||||
verbosityFlag = cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail",
|
||||
Value: 3,
|
||||
}
|
||||
// logFileFlag decides where the logger output is sent. If this flag is left
|
||||
// empty, it will log to stdout.
|
||||
logFileFlag = cli.StringFlag{
|
||||
Name: "log.file",
|
||||
Usage: "Tells the database where to write log entries",
|
||||
}
|
||||
logJSONFormat = cli.BoolFlag{
|
||||
Name: "log.json",
|
||||
Usage: "Tells the database whether log format is json or not",
|
||||
Value: true,
|
||||
}
|
||||
logDebugFlag = cli.BoolFlag{
|
||||
Name: "log.debug",
|
||||
Usage: "Prepends log messages with call-site location (file and line number)",
|
||||
}
|
||||
)
|
||||
@@ -1,83 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
"scroll-tech/common/version"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set up database app info.
|
||||
app := cli.NewApp()
|
||||
app.Name = "db_cli"
|
||||
app.Usage = "The Scroll Database CLI"
|
||||
app.Version = version.Version
|
||||
app.Flags = append(app.Flags, commonFlags...)
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
return utils.Setup(&utils.LogConfig{
|
||||
LogFile: ctx.String(logFileFlag.Name),
|
||||
LogJSONFormat: ctx.Bool(logJSONFormat.Name),
|
||||
LogDebug: ctx.Bool(logDebugFlag.Name),
|
||||
Verbosity: ctx.Int(verbosityFlag.Name),
|
||||
})
|
||||
}
|
||||
|
||||
app.Commands = []*cli.Command{
|
||||
{
|
||||
Name: "reset",
|
||||
Usage: "Clean and reset database.",
|
||||
Action: resetDB,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "Check migration status.",
|
||||
Action: checkDBStatus,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Display the current database version.",
|
||||
Action: dbVersion,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "migrate",
|
||||
Usage: "Migrate the database to the latest version.",
|
||||
Action: migrateDB,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rollback",
|
||||
Usage: "Roll back the database to a previous <version>. Rolls back a single migration if no version specified.",
|
||||
Action: rollbackDB,
|
||||
Flags: []cli.Flag{
|
||||
&configFileFlag,
|
||||
&cli.IntFlag{
|
||||
Name: "version",
|
||||
Usage: "Rollback to the specified version.",
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Run the database.
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,5 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"scroll-tech/common/utils"
|
||||
)
|
||||
|
||||
// DBConfig db config
|
||||
type DBConfig struct {
|
||||
// data source name
|
||||
@@ -16,23 +9,3 @@ type DBConfig struct {
|
||||
MaxOpenNum int `json:"maxOpenNum" default:"200"`
|
||||
MaxIdleNUm int `json:"maxIdleNum" default:"20"`
|
||||
}
|
||||
|
||||
// NewConfig returns a new instance of Config.
|
||||
func NewConfig(file string) (*DBConfig, error) {
|
||||
buf, err := os.ReadFile(filepath.Clean(file))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &DBConfig{}
|
||||
err = json.Unmarshal(buf, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// cover value by env fields
|
||||
cfg.DSN = utils.GetEnvWithDefault("DB_DSN", cfg.DSN)
|
||||
cfg.DriverName = utils.GetEnvWithDefault("DB_DRIVER", cfg.DriverName)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"dsn": "postgres://postgres:123456@localhost:5444/test?sslmode=disable",
|
||||
"driver_name": "postgres"
|
||||
}
|
||||
@@ -8,8 +8,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
)
|
||||
|
||||
var (
|
||||
cli *client.Client
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
cli, err = client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cli.NegotiateAPIVersion(context.Background())
|
||||
}
|
||||
|
||||
// ImgDB the postgres image manager.
|
||||
type ImgDB struct {
|
||||
image string
|
||||
@@ -21,24 +37,24 @@ type ImgDB struct {
|
||||
password string
|
||||
|
||||
running bool
|
||||
*Cmd
|
||||
*docker.Cmd
|
||||
}
|
||||
|
||||
// NewImgDB return postgres db img instance.
|
||||
func NewImgDB(t *testing.T, image, password, dbName string, port int) ImgInstance {
|
||||
func NewImgDB(t *testing.T, image, password, dbName string, port int) docker.ImgInstance {
|
||||
return &ImgDB{
|
||||
image: image,
|
||||
name: fmt.Sprintf("%s-%s_%d", image, dbName, port),
|
||||
password: password,
|
||||
dbName: dbName,
|
||||
port: port,
|
||||
Cmd: NewCmd(t),
|
||||
Cmd: docker.NewCmd(t),
|
||||
}
|
||||
}
|
||||
|
||||
// Start postgres db container.
|
||||
func (i *ImgDB) Start() error {
|
||||
id := GetContainerID(i.name)
|
||||
id := docker.GetContainerID(i.name)
|
||||
if id != "" {
|
||||
return fmt.Errorf("container already exist, name: %s", i.name)
|
||||
}
|
||||
@@ -60,7 +76,7 @@ func (i *ImgDB) Stop() error {
|
||||
|
||||
ctx := context.Background()
|
||||
// check if container is running, stop the running container.
|
||||
id := GetContainerID(i.name)
|
||||
id := docker.GetContainerID(i.name)
|
||||
if id != "" {
|
||||
timeout := time.Second * 3
|
||||
if err := cli.ContainerStop(ctx, id, &timeout); err != nil {
|
||||
@@ -108,7 +124,7 @@ func (i *ImgDB) isOk() bool {
|
||||
select {
|
||||
case <-okCh:
|
||||
time.Sleep(time.Millisecond * 1500)
|
||||
i.id = GetContainerID(i.name)
|
||||
i.id = docker.GetContainerID(i.name)
|
||||
return i.id != ""
|
||||
case <-time.NewTimer(time.Second * 10).C:
|
||||
return false
|
||||
33
database/docker/docker_test.go
Normal file
33
database/docker/docker_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/database"
|
||||
"scroll-tech/database/migrate"
|
||||
)
|
||||
|
||||
func TestDB(t *testing.T) {
|
||||
img := NewImgDB(t, "postgres", "123456", "test", 5433)
|
||||
assert.NoError(t, img.Start())
|
||||
defer img.Stop()
|
||||
|
||||
factory, err := database.NewOrmFactory(&database.DBConfig{
|
||||
DriverName: "postgres",
|
||||
DSN: img.Endpoint(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
db := factory.GetDB()
|
||||
|
||||
version := int64(0)
|
||||
assert.NoError(t, migrate.Rollback(db.DB, &version))
|
||||
|
||||
assert.NoError(t, migrate.Migrate(db.DB))
|
||||
|
||||
vs, err := migrate.Current(db.DB)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Logf("current version:%d", vs)
|
||||
}
|
||||
@@ -3,29 +3,40 @@ module scroll-tech/database
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/mattn/go-sqlite3 v1.14.14
|
||||
github.com/pressly/goose/v3 v3.7.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/btcsuite/btcd v0.20.1-beta // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.13 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/iden3/go-iden3-crypto v0.0.12 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/scroll-tech/zktrie v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
|
||||
github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||
@@ -34,6 +35,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
|
||||
@@ -81,7 +84,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
|
||||
github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
@@ -98,7 +100,15 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE=
|
||||
github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
@@ -138,6 +148,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -170,6 +182,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
@@ -228,6 +243,7 @@ github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F
|
||||
github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
@@ -277,8 +293,11 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||
@@ -294,6 +313,10 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@@ -328,21 +351,19 @@ github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1
|
||||
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6 h1:o0Gq2d8nus6x82apluA5RJwjlOca7LIlpAfxlyvQvxs=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221125025612-4ea77a7577c6/go.mod h1:jurIpDQ0hqtp9//xxeWzr8X9KMP/+TYn+vz3K1wZrv0=
|
||||
github.com/scroll-tech/zktrie v0.3.0 h1:c0GRNELUyAtyuiwllQKT1XjNbs7NRGfguKouiyLfFNY=
|
||||
github.com/scroll-tech/zktrie v0.3.0/go.mod h1:CuJFlG1/soTJJBAySxCZgTF7oPvd5qF6utHOEciC43Q=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d h1:eh1i1M9BKPCQckNFQsV/yfazQ895hevkWr8GuqhKNrk=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20221012120556-b3a7c9b6917d/go.mod h1:SkQ1431r0LkrExTELsapw6JQHYpki8O1mSsObTSmBWU=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
@@ -372,13 +393,13 @@ github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZF
|
||||
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
@@ -428,9 +449,11 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -446,6 +469,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
@@ -456,6 +480,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -513,6 +539,7 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -530,6 +557,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -557,8 +585,11 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -628,6 +659,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -38,14 +38,6 @@ func Rollback(db *sql.DB, version *int64) error {
|
||||
return goose.Down(db, MigrationsDir)
|
||||
}
|
||||
|
||||
// ResetDB clean and migrate db.
|
||||
func ResetDB(db *sql.DB) error {
|
||||
if err := Rollback(db, new(int64)); err != nil {
|
||||
return err
|
||||
}
|
||||
return Migrate(db)
|
||||
}
|
||||
|
||||
// Current get current version
|
||||
func Current(db *sql.DB) (int64, error) {
|
||||
return goose.GetDBVersion(db)
|
||||
|
||||
@@ -8,41 +8,35 @@ import (
|
||||
"github.com/pressly/goose/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/common/utils"
|
||||
|
||||
"scroll-tech/common/docker"
|
||||
|
||||
"scroll-tech/database"
|
||||
docker_db "scroll-tech/database/docker"
|
||||
)
|
||||
|
||||
var (
|
||||
pgDB *sqlx.DB
|
||||
dbImg docker.ImgInstance
|
||||
pgDB *sqlx.DB
|
||||
img docker.ImgInstance
|
||||
)
|
||||
|
||||
func initEnv(t *testing.T) error {
|
||||
// Start db container.
|
||||
dbImg = docker.NewTestDBDocker(t, "postgres")
|
||||
func initEnv(t *testing.T) {
|
||||
img = docker_db.NewImgDB(t, "postgres", "123456", "test_1", 5434)
|
||||
assert.NoError(t, img.Start())
|
||||
|
||||
// Create db orm handler.
|
||||
var err error
|
||||
factory, err := database.NewOrmFactory(&database.DBConfig{
|
||||
DriverName: "postgres",
|
||||
DSN: dbImg.Endpoint(),
|
||||
DriverName: utils.GetEnvWithDefault("TEST_DB_DRIVER", "postgres"),
|
||||
DSN: img.Endpoint(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
pgDB = factory.GetDB()
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMigration(t *testing.T) {
|
||||
defer func() {
|
||||
if dbImg != nil {
|
||||
assert.NoError(t, dbImg.Stop())
|
||||
}
|
||||
}()
|
||||
if err := initEnv(t); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
func TestMegration(t *testing.T) {
|
||||
initEnv(t)
|
||||
defer img.Stop()
|
||||
|
||||
err := Migrate(pgDB.DB)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- TODO: use foreign key for batch_id?
|
||||
-- TODO: why tx_num is bigint?
|
||||
create table block_trace
|
||||
(
|
||||
number BIGINT NOT NULL,
|
||||
hash VARCHAR NOT NULL,
|
||||
parent_hash VARCHAR NOT NULL,
|
||||
trace JSON NOT NULL,
|
||||
batch_id VARCHAR DEFAULT NULL,
|
||||
tx_num INTEGER NOT NULL,
|
||||
gas_used BIGINT NOT NULL,
|
||||
block_timestamp NUMERIC NOT NULL
|
||||
);
|
||||
|
||||
create unique index block_trace_hash_uindex
|
||||
on block_trace (hash);
|
||||
|
||||
create unique index block_trace_number_uindex
|
||||
on block_trace (number);
|
||||
|
||||
create unique index block_trace_parent_uindex
|
||||
on block_trace (number, parent_hash);
|
||||
|
||||
create unique index block_trace_parent_hash_uindex
|
||||
on block_trace (hash, parent_hash);
|
||||
|
||||
create index block_trace_batch_id_index
|
||||
on block_trace (batch_id);
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists block_trace;
|
||||
-- +goose StatementEnd
|
||||
45
database/migrate/migrations/00001_trace.sql
Normal file
45
database/migrate/migrations/00001_trace.sql
Normal file
@@ -0,0 +1,45 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
create table block_result
|
||||
(
|
||||
number integer not null,
|
||||
hash varchar not null,
|
||||
content json not null,
|
||||
proof BYTEA default null,
|
||||
instance_commitments BYTEA default null,
|
||||
status integer default 1,
|
||||
tx_num BIGINT NOT NULL DEFAULT 0,
|
||||
block_timestamp NUMERIC NOT NULL DEFAULT 0,
|
||||
proof_time_sec integer default 0,
|
||||
created_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
comment
|
||||
on column block_result.status is 'undefined, unassigned, skipped, assigned, proved, verified, failed';
|
||||
|
||||
create unique index block_result_hash_uindex
|
||||
on block_result (hash);
|
||||
|
||||
create unique index block_result_number_uindex
|
||||
on block_result (number);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_time = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON block_result FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists block_result;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,48 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
create table l1_message
|
||||
(
|
||||
nonce BIGINT NOT NULL,
|
||||
height BIGINT NOT NULL,
|
||||
sender VARCHAR NOT NULL,
|
||||
target VARCHAR NOT NULL,
|
||||
value VARCHAR NOT NULL,
|
||||
fee VARCHAR NOT NULL,
|
||||
gas_limit BIGINT NOT NULL,
|
||||
deadline BIGINT NOT NULL,
|
||||
calldata TEXT NOT NULL,
|
||||
layer1_hash VARCHAR NOT NULL,
|
||||
layer2_hash VARCHAR DEFAULT NULL,
|
||||
status INTEGER DEFAULT 1,
|
||||
created_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_time TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
comment
|
||||
on column l1_message.status is 'undefined, pending, submitted, confirmed';
|
||||
|
||||
create unique index l1_message_layer1_hash_uindex
|
||||
on l1_message (layer1_hash);
|
||||
|
||||
create index l1_message_height_index
|
||||
on l1_message (height);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_time = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON l1_message FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
drop table if exists l1_message;
|
||||
-- +goose StatementEnd
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user