diff --git a/bcs/ledger/xledger/state/meta/meta.go b/bcs/ledger/xledger/state/meta/meta.go index 4ee96552..f4c7aa0c 100644 --- a/bcs/ledger/xledger/state/meta/meta.go +++ b/bcs/ledger/xledger/state/meta/meta.go @@ -476,7 +476,7 @@ func (t *Meta) LoadGasPrice() (*protos.GasPrice, error) { return nil, ErrProposalParamsIsNegativeNumber } // To be compatible with the old version v3.3 - // If GasPrice configuration is missing or value euqals 0, support a default value + // If GasPrice configuration is missing or value equals 0, support a default value if cpuRate == 0 && memRate == 0 && diskRate == 0 && xfeeRate == 0 { gasPrice = &protos.GasPrice{ CpuRate: 1000, diff --git a/go.mod b/go.mod index 4d5541eb..fd7fdbcf 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/ChainSafe/go-schnorrkel v0.0.0-20200626160457-b38283118816 // indirect + github.com/agiledragon/gomonkey/v2 v2.9.0 github.com/aws/aws-sdk-go v1.32.4 github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d github.com/dgraph-io/badger/v3 v3.2103.1 diff --git a/go.sum b/go.sum index 614e9a17..7f737610 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/agiledragon/gomonkey/v2 v2.9.0 h1:PDiKKybR596O6FHW+RVSG0Z7uGCBNbmbUXh3uCNQ7Hc= +github.com/agiledragon/gomonkey/v2 v2.9.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/jsonschema v0.0.0-20190122210438-a6952de1bbe6/go.mod h1:qpebaTNSsyUn5rPSJMsfqEtDw71TTggXM6stUDI16HA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/kernel/engines/xuperos/engine.go b/kernel/engines/xuperos/engine.go index 63ef9b2b..7dafa5f7 100644 --- a/kernel/engines/xuperos/engine.go +++ b/kernel/engines/xuperos/engine.go @@ -2,7 +2,7 @@ package xuperos import ( "fmt" - "io/ioutil" + "os" "path/filepath" "sync" @@ -14,6 +14,7 @@ import ( engconf "github.com/xuperchain/xupercore/kernel/engines/xuperos/config" xnet "github.com/xuperchain/xupercore/kernel/engines/xuperos/net" "github.com/xuperchain/xupercore/kernel/engines/xuperos/parachain" + "github.com/xuperchain/xupercore/kernel/ledger" "github.com/xuperchain/xupercore/lib/logs" "github.com/xuperchain/xupercore/lib/storage/kvdb" "github.com/xuperchain/xupercore/lib/timer" @@ -162,61 +163,79 @@ func (t *Engine) GetChains() []string { return t.chainM.GetChains() } -// 从本地存储加载链 +/* + 从本地存储加载链 + +Default directories: + + data + └── blockchain + ├── + ├── + │ ... + └── +*/ func (t *Engine) loadChains() error { envCfg := t.engCtx.EnvCfg - dataDir := envCfg.GenDataAbsPath(envCfg.ChainDir) + chainsDir := envCfg.GenDataAbsPath(envCfg.ChainDir) + t.log.Trace("start load chains from blockchain data dir", "dir", chainsDir) - t.log.Trace("start load chain from blockchain data dir", "dir", dataDir) - dir, err := ioutil.ReadDir(dataDir) - if err != nil { - t.log.Error("read blockchain data dir failed", "error", err, "dir", dataDir) - return fmt.Errorf("read blockchain data dir failed") + // 优先加载主链 + if err := t.loadRootChain(chainsDir); err != nil { + return err } - chainCnt := 0 + // 加载平行链 + return t.loadParaChains(chainsDir) +} + +// loadRootChain loads root chain from given directory +func (t *Engine) loadRootChain(chainsDir string) error { rootChain := t.engCtx.EngCfg.RootChain + chainDir := filepath.Join(chainsDir, rootChain) - // 优先加载主链 - for _, fInfo := range dir { - if !fInfo.IsDir() || fInfo.Name() != rootChain { - continue - } - chainDir := filepath.Join(dataDir, fInfo.Name()) - t.log.Trace("start load chain", "chain", fInfo.Name(), "dir", chainDir) - chain, err := LoadChain(t.engCtx, fInfo.Name()) - if err != nil { - t.log.Error("load chain from data dir failed", "error", err, "dir", chainDir) - return err - } - t.log.Trace("load chain from data dir succ", "chain", fInfo.Name()) - - // 记录链实例 - t.chainM.Put(fInfo.Name(), chain) - - // 启动异步任务worker - if fInfo.Name() == rootChain { - aw, err := asyncworker.NewAsyncWorkerImpl(fInfo.Name(), t, chain.ctx.State.GetLDB()) - if err != nil { - t.log.Error("create asyncworker error", "bcName", rootChain, "err", err) - return err - } - chain.ctx.Asyncworker = aw - err = chain.CreateParaChain() - if err != nil { - t.log.Error("create parachain mgmt error", "bcName", rootChain, "err", err) - return fmt.Errorf("create parachain error") - } - if err = aw.Start(); err != nil { - return err - } - } + // check root chain dir + if fi, err := os.Stat(chainDir); err != nil { + return err + } else if !fi.IsDir() { + return fmt.Errorf("load root chain fail: %s is not dir", chainDir) + } - t.log.Trace("load chain succeeded", "chain", fInfo.Name(), "dir", chainDir) - chainCnt++ + // load chain + t.log.Trace("start load chain", "chain", rootChain, "dir", chainDir) + chain, err := LoadChain(t.engCtx, rootChain) + if err != nil { + t.log.Error("load chain from data dir failed", "error", err, "dir", chainDir) + return err + } + t.chainM.Put(rootChain, chain) + t.log.Trace("load chain from data dir succ", "chain", rootChain) + + // start async worker + aw, err := asyncworker.NewAsyncWorkerImpl(rootChain, t, chain.ctx.State.GetLDB()) + if err != nil { + t.log.Error("create asyncworker error", "bcName", rootChain, "err", err) + return err + } + chain.ctx.Asyncworker = aw + err = chain.CreateParaChain() + if err != nil { + t.log.Error("create parachain mgmt error", "bcName", rootChain, "err", err) + return fmt.Errorf("create parachain error") + } + if err = aw.Start(); err != nil { + return err } + t.log.Trace("load root chain succeeded", "chain", rootChain, "dir", chainDir) + return nil +} + +// loadParaChains loads non-root chains from given directory +func (t *Engine) loadParaChains(chainsDir string) error { + // prepare root chain reader // root链必须存在 + rootChain := t.engCtx.EngCfg.RootChain rootChainHandle, err := t.chainM.Get(rootChain) if err != nil { t.log.Error("root chain not exist, please create it first", "rootChain", rootChain) @@ -227,46 +246,68 @@ func (t *Engine) loadChains() error { t.log.Error("root chain get tip reader failed", "err", err.Error()) return err } - // 加载平行链 - for _, fInfo := range dir { + + // load ParaChains + dirs, err := os.ReadDir(chainsDir) + if err != nil { + t.log.Error("read blockchain data dir failed", "error", err, "dir", chainsDir) + return fmt.Errorf("read blockchain data dir failed") + } + + chainCnt := 0 + for _, fInfo := range dirs { if !fInfo.IsDir() || fInfo.Name() == rootChain { continue } - // 通过主链的平行链账本状态,确认是否可以加载该平行链 - group, err := parachain.GetParaChainGroup(rootChainReader, fInfo.Name()) + loaded, err := t.tryLoadParaChain(chainsDir, fInfo.Name(), rootChainReader) if err != nil { - t.log.Error("get para chain group failed", "chain", fInfo.Name(), "err", err.Error()) - if !kvdb.ErrNotFound(err) { - continue - } return err } - - if !group.IsParaChainEnable() { - t.log.Debug("para chain stopped", "chain", fInfo.Name()) - continue + if loaded { + chainCnt++ } + } - chainDir := filepath.Join(dataDir, fInfo.Name()) - t.log.Trace("start load chain", "chain", fInfo.Name(), "dir", chainDir) - chain, err := LoadChain(t.engCtx, fInfo.Name()) - if err != nil { - t.log.Error("load chain from data dir failed", "error", err, "dir", chainDir) - // 平行链加载失败时可以忽略直接跳过运行 - continue + t.log.Trace("load para chain succeeded", "chainCnt", chainCnt) + return nil +} + +// tryLoadParaChain try to load a given ParaChain from given directory, checked by root chain info. +// Returns: +// +// bool: true when a ParaChain is loaded +func (t *Engine) tryLoadParaChain(chainsDir, chainName string, + rootChainReader ledger.XMSnapshotReader) (bool, error) { + + // 通过主链的平行链账本状态,确认是否可以加载该平行链 + group, err := parachain.GetParaChainGroup(rootChainReader, chainName) + if err != nil { + t.log.Error("get para chain group failed", "chain", chainName, "err", err.Error()) + if !kvdb.ErrNotFound(err) { + return false, nil } - t.log.Trace("load chain from data dir succ", "chain", fInfo.Name()) + return false, err + } - // 记录链实例 - t.chainM.Put(fInfo.Name(), chain) + if !group.IsParaChainEnable() { + t.log.Debug("para chain stopped", "chain", chainName) + return false, nil + } - t.log.Trace("load chain succeeded", "chain", fInfo.Name(), "dir", chainDir) - chainCnt++ + chainDir := filepath.Join(chainsDir, chainName) + t.log.Trace("start load chain", "chain", chainName, "dir", chainDir) + chain, err := LoadChain(t.engCtx, chainName) + if err != nil { + t.log.Error("load chain from data dir failed", "error", err, "dir", chainDir) + // 平行链加载失败时可以忽略直接跳过运行 + return false, nil } + // 记录链实例 + t.chainM.Put(chainName, chain) - t.log.Trace("load chain from data dir succeeded", "chainCnt", chainCnt) - return nil + t.log.Trace("load chain succeeded", "chain", chainName, "dir", chainDir) + return true, nil } func (t *Engine) createEngCtx(envCfg *xconf.EnvConf) (*common.EngineCtx, error) { diff --git a/kernel/engines/xuperos/engine_test.go b/kernel/engines/xuperos/engine_test.go index b05f1080..4b88bc52 100644 --- a/kernel/engines/xuperos/engine_test.go +++ b/kernel/engines/xuperos/engine_test.go @@ -1,12 +1,18 @@ package xuperos import ( + "encoding/json" + "errors" "fmt" "log" "os" + "os/exec" + "path/filepath" + "runtime" "testing" - // import要使用的内核核心组件驱动 + "github.com/agiledragon/gomonkey/v2" + _ "github.com/xuperchain/xupercore/bcs/consensus/pow" _ "github.com/xuperchain/xupercore/bcs/consensus/single" _ "github.com/xuperchain/xupercore/bcs/consensus/tdpos" @@ -14,17 +20,19 @@ import ( _ "github.com/xuperchain/xupercore/bcs/contract/evm" _ "github.com/xuperchain/xupercore/bcs/contract/native" _ "github.com/xuperchain/xupercore/bcs/contract/xvm" + xledger "github.com/xuperchain/xupercore/bcs/ledger/xledger/utils" _ "github.com/xuperchain/xupercore/bcs/network/p2pv1" _ "github.com/xuperchain/xupercore/bcs/network/p2pv2" + xconf "github.com/xuperchain/xupercore/kernel/common/xconfig" _ "github.com/xuperchain/xupercore/kernel/contract/kernel" _ "github.com/xuperchain/xupercore/kernel/contract/manager" - _ "github.com/xuperchain/xupercore/lib/crypto/client" - _ "github.com/xuperchain/xupercore/lib/storage/kvdb/leveldb" - - xledger "github.com/xuperchain/xupercore/bcs/ledger/xledger/utils" - xconf "github.com/xuperchain/xupercore/kernel/common/xconfig" "github.com/xuperchain/xupercore/kernel/engines/xuperos/common" + "github.com/xuperchain/xupercore/kernel/engines/xuperos/config" + xnet "github.com/xuperchain/xupercore/kernel/engines/xuperos/net" + "github.com/xuperchain/xupercore/kernel/engines/xuperos/parachain" "github.com/xuperchain/xupercore/kernel/mock" + _ "github.com/xuperchain/xupercore/lib/crypto/client" + _ "github.com/xuperchain/xupercore/lib/storage/kvdb/leveldb" ) func CreateLedger(conf *xconf.EnvConf) error { @@ -73,12 +81,197 @@ func MockEngine(path string) (common.Engine, error) { return eng, nil } -func TestEngine(t *testing.T) { - _, err := MockEngine("p2pv2/node1/conf/env.yaml") +type mockLogger struct { +} + +func (m mockLogger) GetLogId() string { + panic("implement me") +} + +func (m mockLogger) SetCommField(key string, value interface{}) { + panic("implement me") +} + +func (m mockLogger) SetInfoField(key string, value interface{}) { + panic("implement me") +} + +func (m mockLogger) Error(msg string, ctx ...interface{}) { + fmt.Println(msg, ctx) +} + +func (m mockLogger) Warn(msg string, ctx ...interface{}) { + panic("implement me") +} + +func (m mockLogger) Info(msg string, ctx ...interface{}) { + panic("implement me") +} + +func (m mockLogger) Trace(msg string, ctx ...interface{}) { + fmt.Println(msg, ctx) +} + +func (m mockLogger) Debug(msg string, ctx ...interface{}) { + fmt.Println(msg, ctx) +} + +func TestEngine_loadChains(t *testing.T) { + conf, patch := setup(t) + defer patch.Reset() + + type fields struct { + engCtx *common.EngineCtx + netEvent *xnet.NetEvent + relyAgent common.EngineRelyAgent + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "mock data", + fields: fields{ + engCtx: &common.EngineCtx{ + EnvCfg: conf, + EngCfg: &config.EngineConf{ + RootChain: "xuper", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &Engine{ + engCtx: tt.fields.engCtx, + log: new(mockLogger), + netEvent: tt.fields.netEvent, + relyAgent: tt.fields.relyAgent, + } + if err := tr.loadChains(); (err != nil) != tt.wantErr { + t.Errorf("Engine.loadChains() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +type mockRootChainReader struct { +} + +func (m mockRootChainReader) Get(_ string, key []byte) ([]byte, error) { + switch string(key) { + case "ErrNotFount": + return nil, errors.New("not found") + case "ErrOther": + return nil, errors.New("other") + case "Disable": + disabledGroup := parachain.Group{Status: parachain.ParaChainStatusStop} + return json.Marshal(disabledGroup) + default: + enabledGroup := parachain.Group{Status: parachain.ParaChainStatusStart} + return json.Marshal(enabledGroup) + } +} + +func TestEngine_tryLoadParaChain(t *testing.T) { + conf, patch := setup(t) + defer patch.Reset() + + type fields struct { + netEvent *xnet.NetEvent + relyAgent common.EngineRelyAgent + } + type args struct { + chainName string + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr bool + }{ + { + name: "paraChain loaded", + args: args{ + chainName: "xuper", + }, + want: true, + }, + { + name: "paraChain group not fount in rootChain info", + args: args{ + chainName: "ErrNotFount", + }, + want: false, + wantErr: true, + }, + { + name: "get paraChain group error", + args: args{ + chainName: "ErrOther", + }, + want: false, + }, + { + name: "paraChain disabled", + args: args{ + chainName: "Disable", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &Engine{ + engCtx: &common.EngineCtx{ + EnvCfg: conf, + EngCfg: &config.EngineConf{ + RootChain: "non-xuper", + }, + }, + log: new(mockLogger), + netEvent: tt.fields.netEvent, + relyAgent: tt.fields.relyAgent, + } + got, err := tr.tryLoadParaChain(conf.GenDataAbsPath(conf.ChainDir), + tt.args.chainName, + new(mockRootChainReader)) + if (err != nil) != tt.wantErr { + t.Errorf("Engine.tryLoadParaChain() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Engine.tryLoadParaChain() = %v, want %v", got, tt.want) + } + }) + } +} + +func setup(t *testing.T) (*xconf.EnvConf, *gomonkey.Patches) { + conf, err := mock.NewEnvConfForTest("p2pv2/node1/conf/env.yaml") if err != nil { - t.Logf("%v", err) - return + t.Fatalf("new env conf error: %v", err) + } + + RemoveLedger(conf) + if err = CreateLedger(conf); err != nil { + t.Fatal(err) + } + + if runtime.GOOS == "darwin" { + t.Skip() + } + mockLookPath := func(arg string) (string, error) { + if arg == "wasm2c" { + wasm2cPath := filepath.Join(filepath.Dir(os.Args[0]), "wasm2c") + fmt.Println(filepath.Dir(os.Args[0])) + return filepath.Abs(wasm2cPath) + } + return exec.LookPath(arg) } - // go engine.Run() - // engine.Exit() + patch := gomonkey.ApplyFunc(exec.LookPath, mockLookPath) + return conf, patch }