From a44956a05fc6cd7675b6165af3f9a6c90c25b17a Mon Sep 17 00:00:00 2001 From: maskpp Date: Wed, 11 Jan 2023 21:05:46 +0800 Subject: [PATCH 01/11] fix(bug): fix data race in common/cmd module. (#220) --- bridge/cmd/app/app_test.go | 2 +- common/cmd/cmd.go | 79 -------------- common/cmd/cmd_app.go | 114 +++++++++++++++++++++ coordinator/cmd/app/app_test.go | 2 +- database/cmd/app/app_test.go | 2 +- roller/cmd/app/app_test.go | 2 +- tests/integration-test/common.go | 5 +- tests/integration-test/integration_test.go | 9 +- 8 files changed, 124 insertions(+), 91 deletions(-) create mode 100644 common/cmd/cmd_app.go diff --git a/bridge/cmd/app/app_test.go b/bridge/cmd/app/app_test.go index 6ace17211..b2adaf2bf 100644 --- a/bridge/cmd/app/app_test.go +++ b/bridge/cmd/app/app_test.go @@ -15,5 +15,5 @@ func TestRunBridge(t *testing.T) { // wait result bridge.ExpectWithTimeout(true, time.Second*3, fmt.Sprintf("bridge version %s", version.Version)) - bridge.RunApp(false) + bridge.RunApp(nil) } diff --git a/common/cmd/cmd.go b/common/cmd/cmd.go index c9a87be95..e67b4db4e 100644 --- a/common/cmd/cmd.go +++ b/common/cmd/cmd.go @@ -1,17 +1,13 @@ package cmd import ( - "fmt" "os" "os/exec" "strings" "sync" "testing" - "time" - "github.com/docker/docker/pkg/reexec" cmap "github.com/orcaman/concurrent-map" - "github.com/stretchr/testify/assert" ) var verbose bool @@ -51,48 +47,6 @@ func NewCmd(t *testing.T, name string, args ...string) *Cmd { } } -// RunApp exec's the current binary using name as argv[0] which will trigger the -// reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go) -func (t *Cmd) RunApp(parallel bool) { - t.Log("cmd: ", append([]string{t.name}, t.args...)) - cmd := &exec.Cmd{ - Path: reexec.Self(), - Args: append([]string{t.name}, t.args...), - Stderr: t, - Stdout: t, - } - if parallel { - go func() { - _ = cmd.Run() - }() - } else { - _ = cmd.Run() - } - t.mu.Lock() - t.cmd = cmd - t.mu.Unlock() -} - -// WaitExit wait util process exit. -func (t *Cmd) WaitExit() { - // Wait all the check funcs are finished or test status is failed. - for !(t.Failed() || t.checkFuncs.IsEmpty()) { - <-time.After(time.Millisecond * 500) - } - - // Send interrupt signal. - t.mu.Lock() - _ = t.cmd.Process.Signal(os.Interrupt) - t.mu.Unlock() -} - -// Interrupt send interrupt signal. -func (t *Cmd) Interrupt() { - t.mu.Lock() - t.Err = t.cmd.Process.Signal(os.Interrupt) - t.mu.Unlock() -} - // RegistFunc register check func func (t *Cmd) RegistFunc(key string, check checkFunc) { t.checkFuncs.Set(key, check) @@ -103,39 +57,6 @@ func (t *Cmd) UnRegistFunc(key string) { t.checkFuncs.Pop(key) } -// ExpectWithTimeout wait result during timeout time. -func (t *Cmd) ExpectWithTimeout(parallel bool, timeout time.Duration, keyword string) { - if keyword == "" { - return - } - okCh := make(chan struct{}, 1) - t.RegistFunc(keyword, func(buf string) { - if strings.Contains(buf, keyword) { - select { - case okCh <- struct{}{}: - default: - return - } - } - }) - - waitResult := func() { - defer t.UnRegistFunc(keyword) - select { - case <-okCh: - return - case <-time.After(timeout): - assert.Fail(t, fmt.Sprintf("didn't get the desired result before timeout, keyword: %s", keyword)) - } - } - - if parallel { - go waitResult() - } else { - waitResult() - } -} - func (t *Cmd) runCmd() { cmd := exec.Command(t.args[0], t.args[1:]...) //nolint:gosec cmd.Stdout = t diff --git a/common/cmd/cmd_app.go b/common/cmd/cmd_app.go new file mode 100644 index 000000000..bf5fae0e0 --- /dev/null +++ b/common/cmd/cmd_app.go @@ -0,0 +1,114 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + "strings" + "time" + + "github.com/docker/docker/pkg/reexec" + "github.com/stretchr/testify/assert" +) + +// RunApp exec's the current binary using name as argv[0] which will trigger the +// reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go) +func (t *Cmd) RunApp(waitResult func() bool) { + t.Log("cmd: ", append([]string{t.name}, t.args...)) + cmd := &exec.Cmd{ + Path: reexec.Self(), + Args: append([]string{t.name}, t.args...), + Stderr: t, + Stdout: t, + } + if waitResult != nil { + go func() { + _ = cmd.Run() + }() + waitResult() + } else { + _ = cmd.Run() + } + + t.mu.Lock() + t.cmd = cmd + t.mu.Unlock() +} + +// WaitExit wait util process exit. +func (t *Cmd) WaitExit() { + // Wait all the check funcs are finished or test status is failed. + for !(t.Failed() || t.checkFuncs.IsEmpty()) { + <-time.After(time.Millisecond * 500) + } + + // Send interrupt signal. + t.mu.Lock() + _ = t.cmd.Process.Signal(os.Interrupt) + t.mu.Unlock() +} + +// Interrupt send interrupt signal. +func (t *Cmd) Interrupt() { + t.mu.Lock() + t.Err = t.cmd.Process.Signal(os.Interrupt) + t.mu.Unlock() +} + +// WaitResult return true when get the keyword during timeout. +func (t *Cmd) WaitResult(timeout time.Duration, keyword string) bool { + if keyword == "" { + return false + } + okCh := make(chan struct{}, 1) + t.RegistFunc(keyword, func(buf string) { + if strings.Contains(buf, keyword) { + select { + case okCh <- struct{}{}: + default: + return + } + } + }) + defer t.UnRegistFunc(keyword) + select { + case <-okCh: + return true + case <-time.After(timeout): + assert.Fail(t, fmt.Sprintf("didn't get the desired result before timeout, keyword: %s", keyword)) + } + return false +} + +// ExpectWithTimeout wait result during timeout time. +func (t *Cmd) ExpectWithTimeout(parallel bool, timeout time.Duration, keyword string) { + if keyword == "" { + return + } + okCh := make(chan struct{}, 1) + t.RegistFunc(keyword, func(buf string) { + if strings.Contains(buf, keyword) { + select { + case okCh <- struct{}{}: + default: + return + } + } + }) + + waitResult := func() { + defer t.UnRegistFunc(keyword) + select { + case <-okCh: + return + case <-time.After(timeout): + assert.Fail(t, fmt.Sprintf("didn't get the desired result before timeout, keyword: %s", keyword)) + } + } + + if parallel { + go waitResult() + } else { + waitResult() + } +} diff --git a/coordinator/cmd/app/app_test.go b/coordinator/cmd/app/app_test.go index b3d5638b8..f711ab0c3 100644 --- a/coordinator/cmd/app/app_test.go +++ b/coordinator/cmd/app/app_test.go @@ -15,5 +15,5 @@ func TestRunCoordinator(t *testing.T) { // wait result coordinator.ExpectWithTimeout(true, time.Second*3, fmt.Sprintf("coordinator version %s", version.Version)) - coordinator.RunApp(false) + coordinator.RunApp(nil) } diff --git a/database/cmd/app/app_test.go b/database/cmd/app/app_test.go index 14e70498d..2be8a77f9 100644 --- a/database/cmd/app/app_test.go +++ b/database/cmd/app/app_test.go @@ -15,5 +15,5 @@ func TestRunDatabase(t *testing.T) { // wait result bridge.ExpectWithTimeout(true, time.Second*3, fmt.Sprintf("db_cli version %s", version.Version)) - bridge.RunApp(false) + bridge.RunApp(nil) } diff --git a/roller/cmd/app/app_test.go b/roller/cmd/app/app_test.go index 2da3f1d2e..e4987f867 100644 --- a/roller/cmd/app/app_test.go +++ b/roller/cmd/app/app_test.go @@ -15,5 +15,5 @@ func TestRunRoller(t *testing.T) { // wait result roller.ExpectWithTimeout(true, time.Second*3, fmt.Sprintf("roller version %s", version.Version)) - roller.RunApp(false) + roller.RunApp(nil) } diff --git a/tests/integration-test/common.go b/tests/integration-test/common.go index 1b815eaf6..9d3561f74 100644 --- a/tests/integration-test/common.go +++ b/tests/integration-test/common.go @@ -80,7 +80,8 @@ func free(t *testing.T) { } type appAPI interface { - RunApp(parallel bool) + WaitResult(timeout time.Duration, keyword string) bool + RunApp(waitResult func() bool) WaitExit() ExpectWithTimeout(parallel bool, timeout time.Duration, keyword string) } @@ -103,7 +104,7 @@ func runDBCliApp(t *testing.T, option, keyword string) { // Wait expect result. app.ExpectWithTimeout(true, time.Second*3, keyword) - app.RunApp(false) + app.RunApp(nil) } func runRollerApp(t *testing.T, args ...string) appAPI { diff --git a/tests/integration-test/integration_test.go b/tests/integration-test/integration_test.go index 3c024f077..74c309c82 100644 --- a/tests/integration-test/integration_test.go +++ b/tests/integration-test/integration_test.go @@ -28,19 +28,16 @@ func testStartProcess(t *testing.T) { // Start bridge process. bridgeCmd := runBridgeApp(t) - bridgeCmd.ExpectWithTimeout(true, time.Second*20, "Start bridge successfully") - bridgeCmd.RunApp(true) + bridgeCmd.RunApp(func() bool { return bridgeCmd.WaitResult(time.Second*20, "Start bridge successfully") }) // Start coordinator process. coordinatorCmd := runCoordinatorApp(t, "--ws", "--ws.port", "8391") - coordinatorCmd.ExpectWithTimeout(true, time.Second*20, "Start coordinator successfully") - coordinatorCmd.RunApp(true) + coordinatorCmd.RunApp(func() bool { return coordinatorCmd.WaitResult(time.Second*20, "Start coordinator successfully") }) // Start roller process. rollerCmd := runRollerApp(t) - rollerCmd.ExpectWithTimeout(true, time.Second*40, "roller start successfully") rollerCmd.ExpectWithTimeout(true, time.Second*60, "register to coordinator successfully!") - rollerCmd.RunApp(true) + rollerCmd.RunApp(func() bool { return rollerCmd.WaitResult(time.Second*40, "roller start successfully") }) rollerCmd.WaitExit() bridgeCmd.WaitExit() From 65699b89bb72230aea72adb721898b69e1254aba Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Wed, 11 Jan 2023 21:49:37 +0800 Subject: [PATCH 02/11] feat(coordinator): support rollers-per-session (#215) Co-authored-by: colinlyguo Co-authored-by: maskpp Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- coordinator/manager.go | 106 ++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/coordinator/manager.go b/coordinator/manager.go index 615ff5ca6..f643e97d3 100644 --- a/coordinator/manager.go +++ b/coordinator/manager.go @@ -198,7 +198,12 @@ func (m *Manager) restorePrevSessions() { log.Info("Coordinator restart reload sessions", "session start time", time.Unix(sess.info.StartTimestamp, 0)) for _, roller := range sess.info.Rollers { - log.Info("restore roller info for session", "session id", sess.info.ID, "roller name", roller.Name, "public key", roller.PublicKey, "proof status", roller.Status) + log.Info( + "restore roller info for session", + "session id", sess.info.ID, + "roller name", roller.Name, + "public key", roller.PublicKey, + "proof status", roller.Status) } go m.CollectProofs(sess) @@ -274,11 +279,6 @@ func (m *Manager) handleZkProof(pk string, msg *message.ProofDetail) error { "roller pk", roller.PublicKey, "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) - } - // record the failed session. - m.addFailedSession(sess, msg.Error) return nil } @@ -303,8 +303,6 @@ func (m *Manager) handleZkProof(pk string, msg *message.ProofDetail) error { success, err = m.verifier.VerifyProof(msg.Proof) if err != nil { - // record failed session. - m.addFailedSession(sess, 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) @@ -313,21 +311,17 @@ func (m *Manager) handleZkProof(pk string, msg *message.ProofDetail) error { log.Info("Verify zk proof successfully", "verification result", success, "proof id", msg.ID) } - var status orm.ProvingStatus if success { - status = orm.ProvingTaskVerified - } 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 - // so as to re-distribute the task in the future - status = orm.ProvingTaskFailed + if dbErr = m.orm.UpdateProvingStatus(msg.ID, orm.ProvingTaskVerified); dbErr != nil { + log.Error( + "failed to update proving_status", + "msg.ID", msg.ID, + "status", orm.ProvingTaskVerified, + "error", dbErr) + } + return dbErr } - 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) - } - - return dbErr + return nil } // CollectProofs collects proofs corresponding to a proof generation session. @@ -398,12 +392,12 @@ func (m *Manager) APIs() []rpc.API { // StartProofGenerationSession starts a proof generation session func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) (success bool) { - roller := m.selectRoller() - if roller == nil { + if m.GetNumberOfIdleRollers() == 0 { + log.Warn("no idle roller when starting proof generation session", "id", task.ID) return false } - log.Info("start proof generation session", "id", task.ID) + log.Info("start proof generation session", "id", task.ID) defer func() { if !success { if err := m.orm.UpdateProvingStatus(task.ID, orm.ProvingTaskUnassigned); err != nil { @@ -411,11 +405,8 @@ func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) (success boo } } }() - if err := m.orm.UpdateProvingStatus(task.ID, orm.ProvingTaskAssigned); err != nil { - log.Error("failed to update task status", "id", task.ID, "err", err) - return false - } + // Get block traces. blockInfos, err := m.orm.GetBlockInfos(map[string]interface{}{"batch_id": task.ID}) if err != nil { log.Error( @@ -425,7 +416,6 @@ func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) (success boo ) return false } - traces := make([]*types.BlockTrace, len(blockInfos)) for i, blockInfo := range blockInfos { traces[i], err = m.Client.GetBlockTraceByHash(m.ctx, common.HexToHash(blockInfo.Hash)) @@ -440,41 +430,61 @@ func (m *Manager) StartProofGenerationSession(task *orm.BlockBatch) (success boo } } - log.Info("roller is picked", "session id", task.ID, "name", roller.Name, "public key", roller.PublicKey) - - // send trace to roller - if !roller.sendTask(task.ID, traces) { - log.Error("send task failed", "roller name", roller.Name, "public key", roller.PublicKey, "id", task.ID) + // Dispatch task to rollers. + rollers := make(map[string]*orm.RollerStatus) + for i := 0; i < int(m.cfg.RollersPerSession); i++ { + roller := m.selectRoller() + if roller == nil { + break + } + log.Info("roller is picked", "session id", task.ID, "name", roller.Name, "public key", roller.PublicKey) + // send trace to roller + if !roller.sendTask(task.ID, traces) { + log.Error("send task failed", "roller name", roller.Name, "public key", roller.PublicKey, "id", task.ID) + continue + } + rollers[roller.PublicKey] = &orm.RollerStatus{PublicKey: roller.PublicKey, Name: roller.Name, Status: orm.RollerAssigned} + } + // No roller assigned. + if len(rollers) == 0 { + log.Error("no roller assigned", "id", task.ID, "number of idle rollers", m.GetNumberOfIdleRollers()) + return false + } + + // Update session proving status as assigned. + if err := m.orm.UpdateProvingStatus(task.ID, orm.ProvingTaskAssigned); err != nil { + log.Error("failed to update task status", "id", task.ID, "err", err) return false } - pk := roller.PublicKey // Create a proof generation session. - s := &session{ + sess := &session{ info: &orm.SessionInfo{ - ID: task.ID, - Rollers: map[string]*orm.RollerStatus{ - pk: { - PublicKey: pk, - Name: roller.Name, - Status: orm.RollerAssigned, - }, - }, + ID: task.ID, + Rollers: rollers, StartTimestamp: time.Now().Unix(), }, finishChan: make(chan rollerProofStatus, proofAndPkBufferSize), } // Store session info. - if err = m.orm.SetSessionInfo(s.info); err != nil { - log.Error("db set session info fail", "roller name", roller.Name, "public key", pk, "error", err) + if err = m.orm.SetSessionInfo(sess.info); err != nil { + log.Error("db set session info fail", "error", err) + for _, roller := range sess.info.Rollers { + log.Error( + "restore roller info for session", + "session id", sess.info.ID, + "roller name", roller.Name, + "public key", roller.PublicKey, + "proof status", roller.Status) + } return false } m.mu.Lock() - m.sessions[task.ID] = s + m.sessions[task.ID] = sess m.mu.Unlock() - go m.CollectProofs(s) + go m.CollectProofs(sess) return true } From e1247a7eb2fb81badee270f1505d855ab9b4fcbc Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Thu, 12 Jan 2023 03:16:48 +0100 Subject: [PATCH 03/11] refactor(coordinator): remove debug api namespace from manager (#221) Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com> --- coordinator/api_debug.go | 96 ------------------------------------- coordinator/manager.go | 33 +++++-------- coordinator/manager_test.go | 4 -- 3 files changed, 11 insertions(+), 122 deletions(-) delete mode 100644 coordinator/api_debug.go diff --git a/coordinator/api_debug.go b/coordinator/api_debug.go deleted file mode 100644 index 1ab2ee26a..000000000 --- a/coordinator/api_debug.go +++ /dev/null @@ -1,96 +0,0 @@ -package coordinator - -import ( - "fmt" - "time" - - "scroll-tech/database/orm" -) - -// RollerDebugAPI roller api interface in order go get debug message. -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) -} - -// RollerInfo records the roller name, pub key and active session info (id, start time). -type RollerInfo struct { - Name string `json:"name"` - Version string `json:"version"` - PublicKey string `json:"public_key"` - ActiveSession string `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"` - Status string `json:"status"` - StartTime time.Time `json:"start_time"` - FinishTime time.Time `json:"finish_time,omitempty"` // set to 0 if not finished - AssignedRollers []string `json:"assigned_rollers,omitempty"` // roller name list - Error string `json:"error,omitempty"` // empty string if no error encountered -} - -// ListRollers returns all live rollers. -func (m *Manager) ListRollers() ([]*RollerInfo, error) { - m.mu.RLock() - defer m.mu.RUnlock() - var res []*RollerInfo - for _, pk := range m.rollerPool.Keys() { - node, exist := m.rollerPool.Get(pk) - if !exist { - continue - } - roller := node.(*rollerNode) - info := &RollerInfo{ - Name: roller.Name, - Version: roller.Version, - PublicKey: pk, - } - for id, sess := range m.sessions { - if _, ok := sess.info.Rollers[pk]; ok { - info.ActiveSessionStartTime = time.Unix(sess.info.StartTimestamp, 0) - info.ActiveSession = id - break - } - } - res = append(res, info) - } - - return res, nil -} - -func newSessionInfo(sess *session, status orm.ProvingStatus, errMsg string, finished bool) *SessionInfo { - now := time.Now() - var nameList []string - for pk := range sess.info.Rollers { - nameList = append(nameList, sess.info.Rollers[pk].Name) - } - info := SessionInfo{ - ID: sess.info.ID, - Status: status.String(), - AssignedRollers: nameList, - StartTime: time.Unix(sess.info.StartTimestamp, 0), - Error: errMsg, - } - if finished { - info.FinishTime = now - } - return &info -} - -// GetSessionInfo returns the session information given the session id. -func (m *Manager) GetSessionInfo(sessionID string) (*SessionInfo, error) { - m.mu.RLock() - defer m.mu.RUnlock() - if info, ok := m.failedSessionInfos[sessionID]; ok { - return info, nil - } - if s, ok := m.sessions[sessionID]; ok { - return newSessionInfo(s, orm.ProvingTaskAssigned, "", false), nil - } - return nil, fmt.Errorf("no such session, sessionID: %s", sessionID) -} diff --git a/coordinator/manager.go b/coordinator/manager.go index f643e97d3..19d343c96 100644 --- a/coordinator/manager.go +++ b/coordinator/manager.go @@ -65,9 +65,6 @@ type Manager struct { // A map containing proof failed or verify failed proof. rollerPool cmap.ConcurrentMap - // TODO: once put into use, should add to graceful restart. - failedSessionInfos map[string]*SessionInfo - // A direct connection to the Halo2 verifier, used to verify // incoming proofs. verifier *verifier.Verifier @@ -94,15 +91,14 @@ func New(ctx context.Context, cfg *config.RollerManagerConfig, orm database.OrmF log.Info("Start coordinator successfully.") return &Manager{ - ctx: ctx, - cfg: cfg, - rollerPool: cmap.New(), - sessions: make(map[string]*session), - failedSessionInfos: make(map[string]*SessionInfo), - verifier: v, - orm: orm, - Client: client, - tokenCache: cache.New(time.Duration(cfg.TokenTimeToLive)*time.Second, 1*time.Hour), + ctx: ctx, + cfg: cfg, + rollerPool: cmap.New(), + sessions: make(map[string]*session), + verifier: v, + orm: orm, + Client: client, + tokenCache: cache.New(time.Duration(cfg.TokenTimeToLive)*time.Second, 1*time.Hour), }, nil } @@ -279,6 +275,9 @@ func (m *Manager) handleZkProof(pk string, msg *message.ProofDetail) error { "roller pk", roller.PublicKey, "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) + } return nil } @@ -346,7 +345,6 @@ func (m *Manager) CollectProofs(sess *session) { if len(participatingRollers) == 0 { // record failed session. errMsg := "proof generation session ended without receiving any valid proofs" - m.addFailedSession(sess, errMsg) log.Warn(errMsg, "session id", sess.info.ID) // Set status as skipped. // Note that this is only a workaround for testnet here. @@ -382,11 +380,6 @@ func (m *Manager) APIs() []rpc.API { Service: RollerAPI(m), Public: true, }, - { - Namespace: "debug", - Public: true, - Service: RollerDebugAPI(m), - }, } } @@ -506,10 +499,6 @@ func (m *Manager) IsRollerIdle(hexPk string) bool { return true } -func (m *Manager) addFailedSession(sess *session, errMsg string) { - m.failedSessionInfos[sess.info.ID] = newSessionInfo(sess, orm.ProvingTaskFailed, errMsg, true) -} - // VerifyToken verifies pukey for token and expiration time func (m *Manager) VerifyToken(authMsg *message.AuthMsg) (bool, error) { pubkey, _ := authMsg.PublicKey() diff --git a/coordinator/manager_test.go b/coordinator/manager_test.go index 42c16b19c..9aa4e0480 100644 --- a/coordinator/manager_test.go +++ b/coordinator/manager_test.go @@ -318,10 +318,6 @@ func testGracefulRestart(t *testing.T) { }() for i := range ids { - info, err := newRollerManager.GetSessionInfo(ids[i]) - assert.Equal(t, orm.ProvingTaskAssigned.String(), info.Status) - assert.NoError(t, err) - // at this point, roller haven't submitted status, err := l2db.GetProvingStatusByID(ids[i]) assert.NoError(t, err) From dc6b71ca2316f53ded5952d3b58cf9eaba0f7c22 Mon Sep 17 00:00:00 2001 From: Lawliet-Chan <1576710154@qq.com> Date: Thu, 12 Jan 2023 12:55:00 +0800 Subject: [PATCH 04/11] chore: improve golang lint rules (#172) Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- build/.golangci.yml | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/build/.golangci.yml b/build/.golangci.yml index 6e9edbcb8..e051a4537 100644 --- a/build/.golangci.yml +++ b/build/.golangci.yml @@ -179,6 +179,13 @@ linters: - depguard - gocyclo - unparam + - exportloopref + - sqlclosecheck + - rowserrcheck + - durationcheck + - bidichk + - typecheck + - unused enable-all: false disable: @@ -197,38 +204,11 @@ issues: # Excluding configuration per-path, per-linter, per-text and per-source exclude-rules: - # Exclude some linters from running on tests files. - - path: _test\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - # Exclude known linters from partially hard-vendored code, - # which is impossible to exclude via "nolint" comments. - - path: internal/hmac/ - text: "weak cryptographic primitive" - linters: - - gosec - # Exclude some staticcheck messages - linters: - staticcheck text: "SA9003:" - - linters: - - golint - text: "package comment should be of the form" - - - linters: - - golint - text: "don't use ALL_CAPS in Go names;" - - - linters: - - golint - text: "don't use underscores in Go names;" - # Exclude lll issues for long lines with go:generate - linters: - lll From b2a5baa2ad8b5efca6f920416c5eb7059e576f36 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Thu, 12 Jan 2023 16:45:11 +0800 Subject: [PATCH 05/11] feat(bridge): upgrade `TraceGasCost` estimation (#222) --- build/.golangci.yml | 6 ++++++ common/go.mod | 2 +- common/message/message.go | 2 +- common/utils/trace_gas.go | 22 +--------------------- common/utils/trace_gas_test.go | 12 +++--------- common/version/version.go | 8 ++++---- coordinator/Makefile | 2 +- roller/Makefile | 4 ++-- 8 files changed, 19 insertions(+), 39 deletions(-) diff --git a/build/.golangci.yml b/build/.golangci.yml index e051a4537..0545c4a18 100644 --- a/build/.golangci.yml +++ b/build/.golangci.yml @@ -204,6 +204,12 @@ issues: # Excluding configuration per-path, per-linter, per-text and per-source exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - errcheck + - gosec + # Exclude some staticcheck messages - linters: - staticcheck diff --git a/common/go.mod b/common/go.mod index c2d7f2c15..ec70bd6a9 100644 --- a/common/go.mod +++ b/common/go.mod @@ -12,7 +12,6 @@ require ( github.com/scroll-tech/go-ethereum v1.10.14-0.20221221073256-5ca70bf3a257 github.com/stretchr/testify v1.8.0 github.com/urfave/cli/v2 v2.10.2 - golang.org/x/sync v0.1.0 ) require ( @@ -81,6 +80,7 @@ require ( golang.org/x/crypto v0.4.0 // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.3.0 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect diff --git a/common/message/message.go b/common/message/message.go index 13e8b0c28..1ad1970c5 100644 --- a/common/message/message.go +++ b/common/message/message.go @@ -40,7 +40,7 @@ type Identity struct { Timestamp int64 `json:"timestamp"` // Roller public key PublicKey string `json:"publicKey"` - // Version is common.Version+ZK_VERSION. Use the following to check the latest ZK_VERSION version. + // Version is common.Version+ZkVersion. Use the following to check the latest ZkVersion version. // curl -sL https://api.github.com/repos/scroll-tech/scroll-zkevm/commits | jq -r ".[0].sha" Version string `json:"version"` // Random unique token generated by manager diff --git a/common/utils/trace_gas.go b/common/utils/trace_gas.go index 6765b9910..7d72b9320 100644 --- a/common/utils/trace_gas.go +++ b/common/utils/trace_gas.go @@ -1,30 +1,10 @@ package utils import ( - "sync/atomic" - "github.com/scroll-tech/go-ethereum/core/types" - "golang.org/x/sync/errgroup" ) // ComputeTraceGasCost computes gascost based on ExecutionResults.StructLogs.GasCost func ComputeTraceGasCost(trace *types.BlockTrace) uint64 { - var ( - gasCost uint64 - eg errgroup.Group - ) - for idx := range trace.ExecutionResults { - i := idx - eg.Go(func() error { - var sum uint64 - for _, log := range trace.ExecutionResults[i].StructLogs { - sum += log.GasCost - } - atomic.AddUint64(&gasCost, sum) - return nil - }) - } - _ = eg.Wait() - - return gasCost + return trace.Header.GasUsed } diff --git a/common/utils/trace_gas_test.go b/common/utils/trace_gas_test.go index aed1c434c..0e3b0ad38 100644 --- a/common/utils/trace_gas_test.go +++ b/common/utils/trace_gas_test.go @@ -19,13 +19,7 @@ func TestComputeTraceCost(t *testing.T) { blockTrace := &types.BlockTrace{} err = json.Unmarshal(templateBlockTrace, blockTrace) assert.NoError(t, err) - var sum uint64 - for _, v := range blockTrace.ExecutionResults { - for _, sv := range v.StructLogs { - sum += sv.GasCost - } - } - - res := utils.ComputeTraceGasCost(blockTrace) - assert.Equal(t, sum, res) + var expected = blockTrace.Header.GasUsed + got := utils.ComputeTraceGasCost(blockTrace) + assert.Equal(t, expected, got) } diff --git a/common/version/version.go b/common/version/version.go index 0a71a9fab..d3a2f786a 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "prealpha-v9.3" +var tag = "prealpha-v9.4" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { @@ -22,8 +22,8 @@ var commit = func() string { return "" }() -// ZK_VERSION is commit-id of common/libzkp/impl/cargo.lock/scroll-zkevm -var ZK_VERSION string +// ZkVersion is commit-id of common/libzkp/impl/cargo.lock/scroll-zkevm +var ZkVersion string // Version denote the version of scroll protocol, including the l2geth, relayer, coordinator, roller, contracts and etc. -var Version = fmt.Sprintf("%s-%s-%s", tag, commit, ZK_VERSION) +var Version = fmt.Sprintf("%s-%s-%s", tag, commit, ZkVersion) diff --git a/coordinator/Makefile b/coordinator/Makefile index fc9a1bca1..cd14f9dbb 100644 --- a/coordinator/Makefile +++ b/coordinator/Makefile @@ -18,7 +18,7 @@ libzkp: cp -r ../common/libzkp/interface ./verifier/lib coordinator: libzkp ## Builds the Coordinator instance. - go build -ldflags "-X scroll-tech/common/version.ZK_VERSION=${ZK_VERSION}" -o $(PWD)/build/bin/coordinator ./cmd + go build -ldflags "-X scroll-tech/common/version.ZkVersion=${ZK_VERSION}" -o $(PWD)/build/bin/coordinator ./cmd test-verifier: libzkp go test -tags ffi -timeout 0 -v ./verifier diff --git a/roller/Makefile b/roller/Makefile index d56832f9a..a9746919d 100644 --- a/roller/Makefile +++ b/roller/Makefile @@ -14,10 +14,10 @@ libzkp: cp -r ../common/libzkp/interface ./prover/lib roller: libzkp ## Build the Roller instance. - GOBIN=$(PWD)/build/bin go build -ldflags "-X scroll-tech/common/version.ZK_VERSION=${ZK_VERSION}" -o $(PWD)/build/bin/roller ./cmd + GOBIN=$(PWD)/build/bin go build -ldflags "-X scroll-tech/common/version.ZkVersion=${ZK_VERSION}" -o $(PWD)/build/bin/roller ./cmd gpu-roller: libzkp ## Build the GPU Roller instance. - GOBIN=$(PWD)/build/bin go build -ldflags "-X scroll-tech/common/version.ZK_VERSION=${ZK_VERSION}" -tags gpu -o $(PWD)/build/bin/roller ./cmd + GOBIN=$(PWD)/build/bin go build -ldflags "-X scroll-tech/common/version.ZkVersion=${ZK_VERSION}" -tags gpu -o $(PWD)/build/bin/roller ./cmd mock_roller: GOBIN=$(PWD)/build/bin go build -tags mock_prover -o $(PWD)/build/bin/roller $(PWD)/cmd From 54a6ab472afcd3da79a6b6a3bb276aff26e573b7 Mon Sep 17 00:00:00 2001 From: Lawliet-Chan <1576710154@qq.com> Date: Thu, 12 Jan 2023 22:21:59 +0800 Subject: [PATCH 06/11] feat(message): replace `json.Marshal` with `rlp.Encode` for Signing (#219) --- common/message/message.go | 11 +++++------ common/version/version.go | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/common/message/message.go b/common/message/message.go index 1ad1970c5..3b4b73e62 100644 --- a/common/message/message.go +++ b/common/message/message.go @@ -4,12 +4,12 @@ import ( "crypto/ecdsa" "crypto/rand" "encoding/hex" - "encoding/json" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/crypto" + "github.com/scroll-tech/go-ethereum/rlp" ) // RespStatus represents status code from roller to scroll @@ -115,12 +115,11 @@ func (a *AuthMsg) PublicKey() (string, error) { // Hash returns the hash of the auth message, which should be the message used // to construct the Signature. func (i *Identity) Hash() ([]byte, error) { - bs, err := json.Marshal(i) + byt, err := rlp.EncodeToBytes(i) if err != nil { return nil, err } - - hash := crypto.Keccak256Hash(bs) + hash := crypto.Keccak256Hash(byt) return hash[:], nil } @@ -204,12 +203,12 @@ type ProofDetail struct { // Hash return proofMsg content hash. func (z *ProofDetail) Hash() ([]byte, error) { - bs, err := json.Marshal(z) + byt, err := rlp.EncodeToBytes(z) if err != nil { return nil, err } - hash := crypto.Keccak256Hash(bs) + hash := crypto.Keccak256Hash(byt) return hash[:], nil } diff --git a/common/version/version.go b/common/version/version.go index d3a2f786a..8a8b43601 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "prealpha-v9.4" +var tag = "prealpha-v10.0" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { From fcd29c305d95a4d46cbf5b387d946868d6bc2835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 12 Jan 2023 18:24:59 +0100 Subject: [PATCH 07/11] fix(coordinator): use uint32 for timestamp to enable RLP encoding (#225) --- common/message/message.go | 4 ++-- common/message/message_test.go | 2 +- coordinator/manager_test.go | 6 +++--- roller/roller.go | 9 ++++++++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/common/message/message.go b/common/message/message.go index 3b4b73e62..cdd5e227c 100644 --- a/common/message/message.go +++ b/common/message/message.go @@ -36,8 +36,8 @@ type AuthMsg struct { type Identity struct { // Roller name Name string `json:"name"` - // Time of message creation - Timestamp int64 `json:"timestamp"` + // Unverified Unix timestamp of message creation + Timestamp uint32 `json:"timestamp"` // Roller public key PublicKey string `json:"publicKey"` // Version is common.Version+ZkVersion. Use the following to check the latest ZkVersion version. diff --git a/common/message/message_test.go b/common/message/message_test.go index 54b8f862d..24a85be34 100644 --- a/common/message/message_test.go +++ b/common/message/message_test.go @@ -16,7 +16,7 @@ func TestAuthMessageSignAndVerify(t *testing.T) { authMsg := &AuthMsg{ Identity: &Identity{ Name: "testRoller", - Timestamp: time.Now().UnixNano(), + Timestamp: uint32(time.Now().Unix()), }, } assert.NoError(t, authMsg.Sign(privkey)) diff --git a/coordinator/manager_test.go b/coordinator/manager_test.go index 9aa4e0480..e4b860cb7 100644 --- a/coordinator/manager_test.go +++ b/coordinator/manager_test.go @@ -127,7 +127,7 @@ func testFailedHandshake(t *testing.T) { authMsg := &message.AuthMsg{ Identity: &message.Identity{ Name: name, - Timestamp: time.Now().UnixNano(), + Timestamp: uint32(time.Now().Unix()), }, } assert.NoError(t, authMsg.Sign(privkey)) @@ -145,7 +145,7 @@ func testFailedHandshake(t *testing.T) { authMsg = &message.AuthMsg{ Identity: &message.Identity{ Name: name, - Timestamp: time.Now().UnixNano(), + Timestamp: uint32(time.Now().Unix()), }, } assert.NoError(t, authMsg.Sign(privkey)) @@ -415,7 +415,7 @@ func (r *mockRoller) connectToCoordinator() (*client2.Client, ethereum.Subscript authMsg := &message.AuthMsg{ Identity: &message.Identity{ Name: r.rollerName, - Timestamp: time.Now().UnixNano(), + Timestamp: uint32(time.Now().Unix()), }, } _ = authMsg.Sign(r.privKey) diff --git a/roller/roller.go b/roller/roller.go index 3b7039b7f..077ba0e44 100644 --- a/roller/roller.go +++ b/roller/roller.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "errors" "fmt" + "math" "sort" "sync/atomic" "time" @@ -104,10 +105,16 @@ func (r *Roller) Start() { // Register registers Roller to the coordinator through Websocket. func (r *Roller) Register() error { + timestamp := time.Now().Unix() + + if timestamp < 0 || timestamp > math.MaxUint32 { + panic("Expected current time to be between the years 1970 and 2106") + } + authMsg := &message.AuthMsg{ Identity: &message.Identity{ Name: r.cfg.RollerName, - Timestamp: time.Now().UnixMilli(), + Timestamp: uint32(timestamp), PublicKey: r.PublicKey(), Version: version.Version, }, From 0bdcce79ba970330bc07c958e2adeaa81b7db6d7 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Fri, 13 Jan 2023 08:33:53 +0800 Subject: [PATCH 08/11] chore: bump version number (#226) --- common/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/version/version.go b/common/version/version.go index 8a8b43601..7a2503235 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "prealpha-v10.0" +var tag = "prealpha-v10.1" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { From 8f55299941be20b3c2773268036e8d272cde0606 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Fri, 13 Jan 2023 08:41:36 +0800 Subject: [PATCH 09/11] Revert "refactor(coordinator): remove debug api namespace from manager" (#224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nazarii Denha Co-authored-by: Péter Garamvölgyi --- common/version/version.go | 2 +- coordinator/api_debug.go | 96 +++++++++++++++++++++++++++++++++++++ coordinator/manager.go | 33 ++++++++----- coordinator/manager_test.go | 4 ++ 4 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 coordinator/api_debug.go diff --git a/common/version/version.go b/common/version/version.go index 7a2503235..187732a03 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "prealpha-v10.1" +var tag = "prealpha-v10.2" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { diff --git a/coordinator/api_debug.go b/coordinator/api_debug.go new file mode 100644 index 000000000..1ab2ee26a --- /dev/null +++ b/coordinator/api_debug.go @@ -0,0 +1,96 @@ +package coordinator + +import ( + "fmt" + "time" + + "scroll-tech/database/orm" +) + +// RollerDebugAPI roller api interface in order go get debug message. +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) +} + +// RollerInfo records the roller name, pub key and active session info (id, start time). +type RollerInfo struct { + Name string `json:"name"` + Version string `json:"version"` + PublicKey string `json:"public_key"` + ActiveSession string `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"` + Status string `json:"status"` + StartTime time.Time `json:"start_time"` + FinishTime time.Time `json:"finish_time,omitempty"` // set to 0 if not finished + AssignedRollers []string `json:"assigned_rollers,omitempty"` // roller name list + Error string `json:"error,omitempty"` // empty string if no error encountered +} + +// ListRollers returns all live rollers. +func (m *Manager) ListRollers() ([]*RollerInfo, error) { + m.mu.RLock() + defer m.mu.RUnlock() + var res []*RollerInfo + for _, pk := range m.rollerPool.Keys() { + node, exist := m.rollerPool.Get(pk) + if !exist { + continue + } + roller := node.(*rollerNode) + info := &RollerInfo{ + Name: roller.Name, + Version: roller.Version, + PublicKey: pk, + } + for id, sess := range m.sessions { + if _, ok := sess.info.Rollers[pk]; ok { + info.ActiveSessionStartTime = time.Unix(sess.info.StartTimestamp, 0) + info.ActiveSession = id + break + } + } + res = append(res, info) + } + + return res, nil +} + +func newSessionInfo(sess *session, status orm.ProvingStatus, errMsg string, finished bool) *SessionInfo { + now := time.Now() + var nameList []string + for pk := range sess.info.Rollers { + nameList = append(nameList, sess.info.Rollers[pk].Name) + } + info := SessionInfo{ + ID: sess.info.ID, + Status: status.String(), + AssignedRollers: nameList, + StartTime: time.Unix(sess.info.StartTimestamp, 0), + Error: errMsg, + } + if finished { + info.FinishTime = now + } + return &info +} + +// GetSessionInfo returns the session information given the session id. +func (m *Manager) GetSessionInfo(sessionID string) (*SessionInfo, error) { + m.mu.RLock() + defer m.mu.RUnlock() + if info, ok := m.failedSessionInfos[sessionID]; ok { + return info, nil + } + if s, ok := m.sessions[sessionID]; ok { + return newSessionInfo(s, orm.ProvingTaskAssigned, "", false), nil + } + return nil, fmt.Errorf("no such session, sessionID: %s", sessionID) +} diff --git a/coordinator/manager.go b/coordinator/manager.go index 19d343c96..f643e97d3 100644 --- a/coordinator/manager.go +++ b/coordinator/manager.go @@ -65,6 +65,9 @@ type Manager struct { // A map containing proof failed or verify failed proof. rollerPool cmap.ConcurrentMap + // TODO: once put into use, should add to graceful restart. + failedSessionInfos map[string]*SessionInfo + // A direct connection to the Halo2 verifier, used to verify // incoming proofs. verifier *verifier.Verifier @@ -91,14 +94,15 @@ func New(ctx context.Context, cfg *config.RollerManagerConfig, orm database.OrmF log.Info("Start coordinator successfully.") return &Manager{ - ctx: ctx, - cfg: cfg, - rollerPool: cmap.New(), - sessions: make(map[string]*session), - verifier: v, - orm: orm, - Client: client, - tokenCache: cache.New(time.Duration(cfg.TokenTimeToLive)*time.Second, 1*time.Hour), + ctx: ctx, + cfg: cfg, + rollerPool: cmap.New(), + sessions: make(map[string]*session), + failedSessionInfos: make(map[string]*SessionInfo), + verifier: v, + orm: orm, + Client: client, + tokenCache: cache.New(time.Duration(cfg.TokenTimeToLive)*time.Second, 1*time.Hour), }, nil } @@ -275,9 +279,6 @@ func (m *Manager) handleZkProof(pk string, msg *message.ProofDetail) error { "roller pk", roller.PublicKey, "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) - } return nil } @@ -345,6 +346,7 @@ func (m *Manager) CollectProofs(sess *session) { if len(participatingRollers) == 0 { // record failed session. errMsg := "proof generation session ended without receiving any valid proofs" + m.addFailedSession(sess, errMsg) log.Warn(errMsg, "session id", sess.info.ID) // Set status as skipped. // Note that this is only a workaround for testnet here. @@ -380,6 +382,11 @@ func (m *Manager) APIs() []rpc.API { Service: RollerAPI(m), Public: true, }, + { + Namespace: "debug", + Public: true, + Service: RollerDebugAPI(m), + }, } } @@ -499,6 +506,10 @@ func (m *Manager) IsRollerIdle(hexPk string) bool { return true } +func (m *Manager) addFailedSession(sess *session, errMsg string) { + m.failedSessionInfos[sess.info.ID] = newSessionInfo(sess, orm.ProvingTaskFailed, errMsg, true) +} + // VerifyToken verifies pukey for token and expiration time func (m *Manager) VerifyToken(authMsg *message.AuthMsg) (bool, error) { pubkey, _ := authMsg.PublicKey() diff --git a/coordinator/manager_test.go b/coordinator/manager_test.go index e4b860cb7..390bcd0c6 100644 --- a/coordinator/manager_test.go +++ b/coordinator/manager_test.go @@ -318,6 +318,10 @@ func testGracefulRestart(t *testing.T) { }() for i := range ids { + info, err := newRollerManager.GetSessionInfo(ids[i]) + assert.Equal(t, orm.ProvingTaskAssigned.String(), info.Status) + assert.NoError(t, err) + // at this point, roller haven't submitted status, err := l2db.GetProvingStatusByID(ids[i]) assert.NoError(t, err) From 411cb19b62bc46bf5747c580df4912b6f73f4945 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Fri, 13 Jan 2023 11:46:19 +0800 Subject: [PATCH 10/11] chore: add pull_request template (#229) --- .github/pull_request_template.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..9347ad8c5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +1. Purpose or design rationalef or this PR +2. Does this PR involve a new deployment, and involve a new git tag & docker image tag? If so, has `tag` in `common/version.go` been updated? +3. Is this PR a breaking change? If so, have it been attached a `` tag? \ No newline at end of file From 9bd4931f936a939fdd92f6597aeefc9c5572c097 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Fri, 13 Jan 2023 11:50:39 +0800 Subject: [PATCH 11/11] chore: fix typos in pull_request template (#230) --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9347ad8c5..366035858 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,3 @@ -1. Purpose or design rationalef or this PR +1. Purpose or design rationale of this PR 2. Does this PR involve a new deployment, and involve a new git tag & docker image tag? If so, has `tag` in `common/version.go` been updated? -3. Is this PR a breaking change? If so, have it been attached a `` tag? \ No newline at end of file +3. Is this PR a breaking change? If so, have it been attached a `breaking-change` label? \ No newline at end of file