diff options
author | bojie <bojie@dexon.org> | 2019-03-11 17:43:52 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@byzantine-lab.io> | 2019-06-12 17:27:22 +0800 |
commit | 8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461 (patch) | |
tree | 3c54d34e573b97ed4120a33bc5d6593e618a9d49 /dex/app_test.go | |
parent | 04cb64b849988c1cf71fd66a3142f01e8ba7cb23 (diff) | |
download | go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.tar go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.tar.gz go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.tar.bz2 go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.tar.lz go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.tar.xz go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.tar.zst go-tangerine-8ffc8f2b78bfb3cc352f71ca569ad6c09bda1461.zip |
app: new app test flow (#244)
Diffstat (limited to 'dex/app_test.go')
-rw-r--r-- | dex/app_test.go | 2276 |
1 files changed, 2276 insertions, 0 deletions
diff --git a/dex/app_test.go b/dex/app_test.go new file mode 100644 index 000000000..59db1cb22 --- /dev/null +++ b/dex/app_test.go @@ -0,0 +1,2276 @@ +package dex + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "math/rand" + "reflect" + "sync" + "testing" + "time" + + coreCommon "github.com/dexon-foundation/dexon-consensus/common" + coreCrypto "github.com/dexon-foundation/dexon-consensus/core/crypto" + coreEcdsa "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa" + coreTypes "github.com/dexon-foundation/dexon-consensus/core/types" + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/common/math" + "github.com/dexon-foundation/dexon/consensus/dexcon" + "github.com/dexon-foundation/dexon/core" + "github.com/dexon-foundation/dexon/core/rawdb" + "github.com/dexon-foundation/dexon/core/types" + "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/ethdb" + "github.com/dexon-foundation/dexon/event" + "github.com/dexon-foundation/dexon/log" + "github.com/dexon-foundation/dexon/rlp" +) + +type singnal int + +const ( + runFail singnal = iota + runSuccess +) + +type App interface { + PreparePayload(position coreTypes.Position) (payload []byte, err error) + PrepareWitness(height uint64) (witness coreTypes.Witness, err error) + VerifyBlock(block *coreTypes.Block) coreTypes.BlockVerifyStatus + BlockConfirmed(block coreTypes.Block) + BlockDelivered(blockHash coreCommon.Hash, position coreTypes.Position, result coreTypes.FinalizationResult) + SubscribeNewFinalizedBlockEvent(ch chan<- core.NewFinalizedBlockEvent) event.Subscription + Stop() +} + +type Product interface{} + +type Tester interface { + // Name the name of tester + Name() string + + // ViewAndRecord view the data and record then start when requirement is ready. + ViewAndRecord(product Product) + + // ReadyToTest check tester is ready or not. + ReadyToTest() bool + + // InputsForTest return the inputs which we want to test, it will be called only when it is get ready. + InputsForTest(product Product) []reflect.Value + + // ValidateResults validate the results what we expected. + ValidateResults(results []reflect.Value) error + + // Done return true when tester finish it job. + Done() bool + + // StopTime lock all working jobs for test and rollback data if necessary. + StopTime() bool + + // Rollback rollback data in the final. + Rollback() error +} + +type baseTester struct { + App + + ready bool + + testTimer *time.Timer + testInterval time.Duration + + counter int + threshold int + + self interface{} +} + +func (t baseTester) Name() string { + return reflect.TypeOf(t.self).Name() +} + +func (t baseTester) ReadyToTest() bool { + return t.ready +} + +func (t baseTester) Done() bool { + return t.counter >= t.threshold +} + +func (t baseTester) StopTime() bool { + return false +} + +func (t *baseTester) Rollback() error { + return nil +} + +func (t *baseTester) ViewAndRecord(product Product) { + panic("need to implement") +} + +func (t baseTester) InputsForTest(product Product) []reflect.Value { + panic("need to implement") +} + +func (t *baseTester) ValidateResults(results []reflect.Value) error { + panic("need to implement") +} + +type takerName string + +type makerName string + +type ProductCenter struct { + takerChan map[takerName]chan Product + + takerList map[makerName]map[takerName]struct{} +} + +// RequestProduct make a blocking request the product from maker. +func (center *ProductCenter) RequestProduct(tName takerName) Product { + p := <-center.takerChan[tName] + return p +} + +// DeliverProduct deliver product for takers. +func (center *ProductCenter) DeliverProduct(mName makerName, product Product) { + for tName := range center.takerList[mName] { + center.takerChan[tName] <- product + } +} + +// Register build the connection between taker and maker. +func (center *ProductCenter) Register(tName takerName, mName ...makerName) { + center.takerChan[tName] = make(chan Product, 1000) + + for _, n := range mName { + if _, exist := center.takerList[n]; !exist { + center.takerList[n] = make(map[takerName]struct{}) + center.takerList[n][tName] = struct{}{} + } else { + center.takerList[n][tName] = struct{}{} + } + } +} + +func (center ProductCenter) New() *ProductCenter { + center.takerChan = map[takerName]chan Product{} + center.takerList = map[makerName]map[takerName]struct{}{} + return ¢er +} + +type FactoryBase struct { + App + + targetFunc interface{} + + name string + + center *ProductCenter + + testers []Tester + + status chan map[singnal]interface{} + + stopTimeMu *sync.RWMutex +} + +func (base *FactoryBase) testerDoWork(product Product) error { + for _, t := range base.testers { + if t.Done() { + continue + } + + if err := func() (tErr error) { + var returns []reflect.Value + defer func() { + r := recover() + if r != nil { + returns = append(returns, reflect.ValueOf(fmt.Errorf("%v", r))) + } + + if t.ReadyToTest() { + err := t.ValidateResults(returns) + if err != nil { + tErr = err + return + } + + err = t.Rollback() + if err != nil { + tErr = fmt.Errorf("recover fail: %v", tErr) + return + } + } else if r != nil { + tErr = fmt.Errorf("%v", r) + } + + if t.StopTime() { + base.stopTimeMu.Unlock() + } else { + base.stopTimeMu.RUnlock() + } + }() + + if t.StopTime() { + base.stopTimeMu.Lock() + } else { + base.stopTimeMu.RLock() + } + t.ViewAndRecord(product) + if t.ReadyToTest() { + inputs := t.InputsForTest(product) + returns = reflect.ValueOf(base.targetFunc).Call(inputs) + } + return + }(); err != nil { + return fmt.Errorf("%s: %v", t.Name(), err) + } + } + + return nil +} + +func (base *FactoryBase) testerAllDone() bool { + for _, t := range base.testers { + if !t.Done() { + return false + } + } + + return true +} + +func (base *FactoryBase) notifySuccess() { + base.status <- map[singnal]interface{}{runSuccess: nil} +} + +func (base *FactoryBase) notifyFail(msg interface{}) { + base.status <- map[singnal]interface{}{runFail: msg} +} + +type ConfigFactory struct { + FactoryBase + + initialized bool + + sleepTime time.Duration + + masterKey *ecdsa.PrivateKey +} + +func (f *ConfigFactory) Run() { + for { + if !f.initialized { + // Initial block for first round. + go f.center.DeliverProduct(makerName(f.name), + &PositionProduct{position: coreTypes.Position{ + Round: 0, + Height: 0, + }}) + + f.initialized = true + continue + } + + time.Sleep(f.sleepTime) + + product := f.center.RequestProduct(takerName(f.name)) + position := f.covertProduct(product) + position.Height++ + + if f.roundStartAt(position.Round+1) == position.Height { + position.Round = position.Round + 1 + } + + go f.center.DeliverProduct(makerName(f.name), &PositionProduct{ + position: position, + }) + } +} + +func (f ConfigFactory) covertProduct(product interface{}) coreTypes.Position { + var position coreTypes.Position + switch product.(type) { + case *BlockConfirmedProduct: + position = product.(*BlockConfirmedProduct).block.Position + default: + panic(fmt.Errorf("unexpected type %T", product)) + } + + return position +} + +func (f *ConfigFactory) roundStartAt(round uint64) uint64 { + dexonApp := f.App.(*DexconApp) + start := uint64(0) + for i := uint64(0); i < round; i++ { + start += dexonApp.gov.Configuration(i).RoundLength + } + + return start - 1 +} + +func (f ConfigFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex, masterKey *ecdsa.PrivateKey) *ConfigFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + stopTimeMu: stopTimeMu, + } + f.sleepTime = 250 * time.Millisecond + f.masterKey = masterKey + f.center.Register(takerName(f.name), makerName(reflect.TypeOf(BlockConfirmedFactory{}).Name())) + return &f +} + +type PositionProduct struct { + position coreTypes.Position +} + +type PreparePayloadFactory struct { + FactoryBase +} + +func (f *PreparePayloadFactory) Run() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + for { + product := f.center.RequestProduct(takerName(f.name)) + + if len(f.testers) > 0 && f.testerAllDone() { + f.notifySuccess() + f.testers = nil + } else if err := f.testerDoWork(product); err != nil { + panic(fmt.Errorf("test fail: %v", err)) + } + + go func() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + position := f.covertProduct(product) + f.stopTimeMu.RLock() + payload, err := f.App.PreparePayload(position) + if err != nil { + panic(err) + } + f.stopTimeMu.RUnlock() + + go f.center.DeliverProduct(makerName(f.name), &PreparePayloadProduct{ + position: position, + payload: payload, + }) + }() + } +} + +func (f PreparePayloadFactory) covertProduct(product interface{}) coreTypes.Position { + var position coreTypes.Position + switch product.(type) { + case *PositionProduct: + position = product.(*PositionProduct).position + default: + panic(fmt.Errorf("unexpected type %T", product)) + } + + return position +} + +func (f PreparePayloadFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *PreparePayloadFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + targetFunc: app.PreparePayload, + status: make(chan map[singnal]interface{}, 1), + stopTimeMu: stopTimeMu, + } + f.center.Register(takerName(f.name), makerName(reflect.TypeOf(ConfigFactory{}).Name())) + return &f +} + +func (f PreparePayloadFactory) NewWithTester(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *PreparePayloadFactory { + factory := f.New(app, center, stopTimeMu) + factory.testers = []Tester{ + ppBlockLimitTester{}.New(app, 10, 3, 3), + ppBlockHeightTester{}.New(app, 20, 3, 3), + } + return factory +} + +type PreparePayloadProduct struct { + position coreTypes.Position + payload []byte +} + +type ppBlockLimitTester struct { + baseTester + + round uint64 +} + +func (t ppBlockLimitTester) New(app App, startAt, interval, threshold int) *ppBlockLimitTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *ppBlockLimitTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PositionProduct: + t.round = product.(*PositionProduct).position.Round + t.ready = true + } + + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t ppBlockLimitTester) InputsForTest(product Product) []reflect.Value { + return []reflect.Value{reflect.ValueOf(product.(*PositionProduct).position)} +} + +func (t *ppBlockLimitTester) ValidateResults(results []reflect.Value) error { + if len(results) > 2 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[1].Interface().(type) { + case nil: + case error: + return fmt.Errorf("result[1] must nil: %v", results[1].Interface()) + default: + return fmt.Errorf("unexpect results[1] return type %T", results[1].Interface()) + } + + switch results[0].Interface().(type) { + case []byte: + if results[0].Bytes() != nil { + var txs []*types.Transaction + err := rlp.DecodeBytes(results[0].Bytes(), &txs) + if err != nil { + return fmt.Errorf("rlp decode error: %v", err) + } + + app := t.App.(*DexconApp) + blockLimit := app.gov.DexconConfiguration(t.round).BlockGasLimit + totalGas := uint64(0) + for _, tx := range txs { + totalGas += tx.Gas() + } + + if blockLimit < totalGas { + return fmt.Errorf("total cost larger than block limit %d < %d", blockLimit, totalGas) + } + + t.counter++ + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.ready = false + return nil +} + +type ppBlockHeightTester struct { + baseTester + + height uint64 +} + +func (t ppBlockHeightTester) New(app App, startAt, interval, threshold int) *ppBlockHeightTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *ppBlockHeightTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PositionProduct: + t.height = product.(*PositionProduct).position.Height + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t ppBlockHeightTester) InputsForTest(product Product) []reflect.Value { + position := product.(*PositionProduct).position + position.Height-- + return []reflect.Value{reflect.ValueOf(position)} +} + +func (t *ppBlockHeightTester) ValidateResults(results []reflect.Value) error { + if len(results) > 2 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[1].Interface().(type) { + case error: + expectErr := fmt.Sprintf("expected height %d but get %d", t.height, t.height-1) + if results[1].Interface().(error).Error() != expectErr { + return fmt.Errorf("unexpected error msg: %v", results[1].Interface()) + } + default: + return fmt.Errorf("unexpect results[1] return type %T", results[1].Interface()) + } + + switch results[0].Interface().(type) { + case []byte: + if results[0].Bytes() != nil { + return fmt.Errorf("payload should be nil") + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.ready = false + t.counter++ + return nil +} + +type PrepareWitnessFactory struct { + FactoryBase +} + +func (f *PrepareWitnessFactory) Run() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + for { + product := f.center.RequestProduct(takerName(f.name)) + + if len(f.testers) > 0 && f.testerAllDone() { + f.notifySuccess() + f.testers = nil + } else if err := f.testerDoWork(product); err != nil { + panic(fmt.Errorf("test fail: %v", err)) + } + + go func() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + f.stopTimeMu.RLock() + witness, err := f.App.PrepareWitness(f.App.(*DexconApp).blockchain.CurrentBlock().NumberU64()) + if err != nil { + panic(err) + } + f.stopTimeMu.RUnlock() + + position, payload := f.convertProduct(product) + go f.center.DeliverProduct(makerName(f.name), &PrepareWitnessProduct{ + block: coreTypes.Block{ + Hash: coreCommon.NewRandomHash(), + ProposerID: coreTypes.NodeID{coreCommon.Hash{1, 2, 3}}, + Position: position, + Witness: witness, + Payload: payload, + PayloadHash: coreCrypto.Keccak256Hash(payload), + }, + }) + }() + } +} + +func (f PrepareWitnessFactory) convertProduct(product Product) (coreTypes.Position, []byte) { + var ( + position coreTypes.Position + payload []byte + ) + switch product.(type) { + case *PreparePayloadProduct: + realProduct := product.(*PreparePayloadProduct) + position = realProduct.position + payload = realProduct.payload + default: + panic(fmt.Errorf("unexpected type %T", product)) + } + + return position, payload +} + +func (f PrepareWitnessFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *PrepareWitnessFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + targetFunc: app.PrepareWitness, + status: make(chan map[singnal]interface{}, 1), + stopTimeMu: stopTimeMu, + } + f.center.Register(takerName(f.name), makerName(reflect.TypeOf(PreparePayloadFactory{}).Name())) + return &f +} + +func (f PrepareWitnessFactory) NewWithTester(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *PrepareWitnessFactory { + factory := f.New(app, center, stopTimeMu) + factory.testers = []Tester{ + pwConsensusHeightTester{}.New(app, 10, 10, 3), + } + + return factory +} + +type PrepareWitnessProduct struct { + block coreTypes.Block +} + +type pwConsensusHeightTester struct { + baseTester +} + +func (t pwConsensusHeightTester) New(app App, startAt, interval, threshold int) *pwConsensusHeightTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *pwConsensusHeightTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + t.ready = true + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t pwConsensusHeightTester) InputsForTest(product Product) []reflect.Value { + return []reflect.Value{reflect.ValueOf(uint64(99999))} +} + +func (t *pwConsensusHeightTester) ValidateResults(results []reflect.Value) error { + if len(results) > 2 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[1].Interface().(type) { + case nil: + return fmt.Errorf("results[1] must not nil") + case error: + if results[1].Interface().(error).Error() != "current height < consensus height" { + return fmt.Errorf("unexpected error: %v", results[1].Interface()) + } + default: + return fmt.Errorf("unexpect results[1] return type %T", results[1].Interface()) + } + + switch results[0].Interface().(type) { + case coreTypes.Witness: + witness := results[0].Interface().(coreTypes.Witness) + if witness.Height != 0 || len(witness.Data) > 0 { + return fmt.Errorf("unexpected results[1] return %+v", results[0].Interface()) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type VerifyBlockFactory struct { + FactoryBase +} + +func (f *VerifyBlockFactory) Run() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + for { + product := f.center.RequestProduct(takerName(f.name)) + + if len(f.testers) > 0 && f.testerAllDone() { + f.notifySuccess() + f.testers = nil + } else if err := f.testerDoWork(product); err != nil { + panic(fmt.Errorf("test fail: %v", err)) + } + + go func() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + block := f.convertProduct(product) + + f.stopTimeMu.RLock() + if status := f.App.VerifyBlock(&block); status != coreTypes.VerifyOK { + panic(fmt.Errorf("verify block fail: status %v", status)) + } + f.stopTimeMu.RUnlock() + + go f.center.DeliverProduct(makerName(f.name), &VerifyBlockProduct{ + block: block, + }) + }() + } +} + +func (f VerifyBlockFactory) convertProduct(product Product) coreTypes.Block { + var block coreTypes.Block + switch product.(type) { + case *PrepareWitnessProduct: + block = product.(*PrepareWitnessProduct).block + default: + panic(fmt.Errorf("unexpected type %T", product)) + } + + return block +} + +func (f VerifyBlockFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *VerifyBlockFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + targetFunc: app.VerifyBlock, + status: make(chan map[singnal]interface{}, 1), + stopTimeMu: stopTimeMu, + } + f.center.Register(takerName(f.name), makerName(reflect.TypeOf(PrepareWitnessFactory{}).Name())) + return &f +} + +func (f VerifyBlockFactory) NewWithTester(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *VerifyBlockFactory { + factory := f.New(app, center, stopTimeMu) + factory.testers = []Tester{ + vbWitnessDataDecodeTester{}.New(app, 10, 5, 3), + vbWitnessHeightTester{}.New(app, 20, 5, 3), + vbWitnessDataTester{}.New(app, 30, 5, 3), + vbBlockHeightTester{}.New(app, 40, 3, 3), + vbPayloadDecodeTester{}.New(app, 50, 5, 3), + vbTxNonceSequenceTester{}.New(app, 60, 5, 3), + vbTxNonceIncrementTester{}.New(app, 70, 5, 3), + vbTxIntrinsicGasTester{}.New(app, 80, 5, 3), + vbTxGasTooLowTester{}.New(app, 90, 5, 3), + vbInsufficientFundsTester{}.New(app, 100, 5, 3), + vbBlockLimitTester{}.New(app, 110, 5, 3), + } + + return factory +} + +type VerifyBlockProduct struct { + block coreTypes.Block +} + +type vbWitnessDataDecodeTester struct { + baseTester +} + +func (t vbWitnessDataDecodeTester) New(app App, startAt, interval, threshold int) *vbWitnessDataDecodeTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbWitnessDataDecodeTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbWitnessDataDecodeTester) InputsForTest(product Product) []reflect.Value { + block := product.(*PrepareWitnessProduct).block + block.Witness.Data = make([]byte, 100) + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbWitnessDataDecodeTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + if results[0].Interface().(coreTypes.BlockVerifyStatus) != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpected status %v", results[0].Interface()) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbWitnessHeightTester struct { + baseTester +} + +func (t vbWitnessHeightTester) New(app App, startAt, interval, threshold int) *vbWitnessHeightTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbWitnessHeightTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbWitnessHeightTester) InputsForTest(product Product) []reflect.Value { + block := product.(*PrepareWitnessProduct).block + block.Witness.Height += uint64(rand.New(rand.NewSource(time.Now().UnixNano())).Intn(10) + 1) + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbWitnessHeightTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + if results[0].Interface().(coreTypes.BlockVerifyStatus) != coreTypes.VerifyRetryLater { + return fmt.Errorf("unexpected status %v", results[0].Interface()) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbWitnessDataTester struct { + baseTester +} + +func (t vbWitnessDataTester) New(app App, startAt, interval, threshold int) *vbWitnessDataTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbWitnessDataTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbWitnessDataTester) InputsForTest(product Product) []reflect.Value { + block := product.(*PrepareWitnessProduct).block + randNum := big.NewInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63()) + var err error + block.Witness.Data, err = rlp.EncodeToBytes(common.BigToHash(randNum)) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbWitnessDataTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + if results[0].Interface().(coreTypes.BlockVerifyStatus) != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpected status %v", results[0].Interface()) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbBlockHeightTester struct { + baseTester +} + +func (t vbBlockHeightTester) New(app App, startAt, interval, threshold int) *vbBlockHeightTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbBlockHeightTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbBlockHeightTester) InputsForTest(product Product) []reflect.Value { + block := product.(*PrepareWitnessProduct).block + block.Position.Height-- + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbBlockHeightTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + if results[0].Interface().(coreTypes.BlockVerifyStatus) != coreTypes.VerifyRetryLater { + return fmt.Errorf("unexpected status %v", results[0].Interface()) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbPayloadDecodeTester struct { + baseTester +} + +func (t vbPayloadDecodeTester) New(app App, startAt, interval, threshold int) *vbPayloadDecodeTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbPayloadDecodeTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbPayloadDecodeTester) InputsForTest(product Product) []reflect.Value { + block := product.(*PrepareWitnessProduct).block + block.Payload = []byte{0x00} + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbPayloadDecodeTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbTxNonceSequenceTester struct { + baseTester +} + +func (t vbTxNonceSequenceTester) New(app App, startAt, interval, threshold int) *vbTxNonceSequenceTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbTxNonceSequenceTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbTxNonceSequenceTester) InputsForTest(product Product) []reflect.Value { + app := t.App.(*DexconApp) + block := product.(*PrepareWitnessProduct).block + var key *ecdsa.PrivateKey + var err error + key, err = crypto.GenerateKey() + if err != nil { + panic(err) + } + + blockchain := app.blockchain + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + var txs []*types.Transaction + for i := uint64(0); i < 3; i++ { + if i == 1 { + continue + } + + tx, err := types.SignTx(types.NewTransaction(i, common.Address{}, nil, 21000, nil, nil), signer, key) + if err != nil { + panic(err) + } + txs = append(txs, tx) + } + + block.Payload, err = rlp.EncodeToBytes(txs) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbTxNonceSequenceTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbTxNonceIncrementTester struct { + baseTester +} + +func (t vbTxNonceIncrementTester) New(app App, startAt, interval, threshold int) *vbTxNonceIncrementTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbTxNonceIncrementTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbTxNonceIncrementTester) InputsForTest(product Product) []reflect.Value { + app := t.App.(*DexconApp) + block := product.(*PrepareWitnessProduct).block + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + + blockchain := app.blockchain + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + var txs []*types.Transaction + for i := uint64(1); i < 4; i++ { + tx, err := types.SignTx(types.NewTransaction(i, common.Address{}, nil, 21000, nil, nil), signer, key) + if err != nil { + panic(err) + } + txs = append(txs, tx) + } + + block.Payload, err = rlp.EncodeToBytes(txs) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbTxNonceIncrementTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbTxIntrinsicGasTester struct { + baseTester +} + +func (t vbTxIntrinsicGasTester) New(app App, startAt, interval, threshold int) *vbTxIntrinsicGasTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbTxIntrinsicGasTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbTxIntrinsicGasTester) InputsForTest(product Product) []reflect.Value { + app := t.App.(*DexconApp) + block := product.(*PrepareWitnessProduct).block + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + + blockchain := app.blockchain + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + var txs []*types.Transaction + for i := uint64(0); i < 3; i++ { + tx, err := types.SignTx(types.NewTransaction(i, common.Address{}, nil, 10000, nil, nil), signer, key) + if err != nil { + panic(err) + } + txs = append(txs, tx) + } + + block.Payload, err = rlp.EncodeToBytes(txs) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbTxIntrinsicGasTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbTxGasTooLowTester struct { + baseTester +} + +func (t vbTxGasTooLowTester) New(app App, startAt, interval, threshold int) *vbTxGasTooLowTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbTxGasTooLowTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbTxGasTooLowTester) InputsForTest(product Product) []reflect.Value { + app := t.App.(*DexconApp) + block := product.(*PrepareWitnessProduct).block + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + + blockchain := app.blockchain + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + var txs []*types.Transaction + for i := uint64(0); i < 3; i++ { + tx, err := types.SignTx(types.NewTransaction(i, common.Address{}, nil, 21000, nil, []byte{0x00}), signer, key) + if err != nil { + panic(err) + } + txs = append(txs, tx) + } + + block.Payload, err = rlp.EncodeToBytes(txs) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbTxGasTooLowTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbInsufficientFundsTester struct { + baseTester +} + +func (t vbInsufficientFundsTester) New(app App, startAt, interval, threshold int) *vbInsufficientFundsTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbInsufficientFundsTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbInsufficientFundsTester) InputsForTest(product Product) []reflect.Value { + app := t.App.(*DexconApp) + block := product.(*PrepareWitnessProduct).block + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + + blockchain := app.blockchain + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + var txs []*types.Transaction + for i := uint64(0); i < 3; i++ { + tx, err := types.SignTx(types.NewTransaction(i, common.Address{}, big.NewInt(1), 21000, nil, nil), signer, key) + if err != nil { + panic(err) + } + txs = append(txs, tx) + } + + block.Payload, err = rlp.EncodeToBytes(txs) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbInsufficientFundsTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type vbBlockLimitTester struct { + baseTester +} + +func (t vbBlockLimitTester) New(app App, startAt, interval, threshold int) *vbBlockLimitTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *vbBlockLimitTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *PrepareWitnessProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t vbBlockLimitTester) InputsForTest(product Product) []reflect.Value { + app := t.App.(*DexconApp) + block := product.(*PrepareWitnessProduct).block + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + + blockchain := app.blockchain + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + var txs []*types.Transaction + for i := uint64(0); i < 3; i++ { + tx, err := types.SignTx(types.NewTransaction(i, common.Address{}, nil, 10e10, nil, nil), signer, key) + if err != nil { + panic(err) + } + txs = append(txs, tx) + } + + block.Payload, err = rlp.EncodeToBytes(txs) + if err != nil { + panic(err) + } + + return []reflect.Value{reflect.ValueOf(&block)} +} + +func (t *vbBlockLimitTester) ValidateResults(results []reflect.Value) error { + if len(results) > 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case coreTypes.BlockVerifyStatus: + status := results[0].Interface().(coreTypes.BlockVerifyStatus) + if status != coreTypes.VerifyInvalidBlock { + return fmt.Errorf("unexpect status %v", status) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type BlockConfirmedFactory struct { + FactoryBase + + masterKey *coreEcdsa.PrivateKey +} + +func (f *BlockConfirmedFactory) Run() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + for { + product := f.center.RequestProduct(takerName(f.name)) + + if len(f.testers) > 0 && f.testerAllDone() { + f.notifySuccess() + f.testers = nil + } else if err := f.testerDoWork(product); err != nil { + panic(fmt.Errorf("test fail: %v", err)) + } + + go func() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + block := f.convertProduct(product) + block.ProposerID = coreTypes.NewNodeID(f.masterKey.PublicKey()) + f.stopTimeMu.RLock() + f.App.BlockConfirmed(block) + f.stopTimeMu.RUnlock() + + block.Finalization = coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: block.Position.Height + 1, + } + + f.center.DeliverProduct(makerName(f.name), &BlockConfirmedProduct{ + block: block, + }) + }() + } +} + +func (f BlockConfirmedFactory) convertProduct(product Product) coreTypes.Block { + var block coreTypes.Block + switch product.(type) { + case *VerifyBlockProduct: + block = product.(*VerifyBlockProduct).block + default: + panic(fmt.Errorf("unexpected type %T", product)) + } + + return block +} + +func (f BlockConfirmedFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex, + masterKey *ecdsa.PrivateKey) *BlockConfirmedFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + targetFunc: app.BlockConfirmed, + status: make(chan map[singnal]interface{}, 1), + stopTimeMu: stopTimeMu, + } + f.masterKey = coreEcdsa.NewPrivateKeyFromECDSA(masterKey) + f.center.Register(takerName(f.name), makerName(reflect.TypeOf(VerifyBlockFactory{}).Name())) + return &f +} + +func (f BlockConfirmedFactory) NewWithTester(app App, center *ProductCenter, stopTimeMu *sync.RWMutex, + masterKey *ecdsa.PrivateKey) *BlockConfirmedFactory { + factory := f.New(app, center, stopTimeMu, masterKey) + factory.testers = []Tester{ + bcBlockConfirmedTester{}.New(app, 30, 5, 3), + } + + return factory +} + +type BlockConfirmedProduct struct { + block coreTypes.Block +} + +type addInfo struct { + nonce *uint64 + cost *big.Int + counter *uint64 +} + +type bcBlockConfirmedTester struct { + baseTester + + block coreTypes.Block + originAddrInfo map[common.Address]addInfo +} + +func (t bcBlockConfirmedTester) New(app App, startAt, interval, threshold int) *bcBlockConfirmedTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + t.originAddrInfo = map[common.Address]addInfo{} + return &t +} + +func (t *bcBlockConfirmedTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *VerifyBlockProduct: + t.block = product.(*VerifyBlockProduct).block + var txs []*types.Transaction + err := rlp.DecodeBytes(t.block.Payload, &txs) + if err != nil { + panic(err) + } else if len(txs) > 0 { + app := t.App.(*DexconApp) + blockchain := app.blockchain + for _, tx := range txs { + msg, err := tx.AsMessage(types.MakeSigner(blockchain.Config(), new(big.Int))) + if err != nil { + panic(err) + } + + if _, exist := t.originAddrInfo[msg.From()]; !exist { + info := addInfo{} + + nonce, exist := app.addressNonce[msg.From()] + if !exist { + info.nonce = nil + } else { + info.nonce = &nonce + } + + cost, exist := app.addressCost[msg.From()] + if !exist { + info.cost = nil + } else { + info.cost = cost + } + + counter, exist := app.addressCounter[msg.From()] + if !exist { + info.counter = nil + } else { + info.counter = &counter + } + + t.originAddrInfo[msg.From()] = info + } + } + t.ready = true + } + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t bcBlockConfirmedTester) InputsForTest(product Product) []reflect.Value { + return []reflect.Value{reflect.ValueOf(product.(*VerifyBlockProduct).block)} +} + +func (t *bcBlockConfirmedTester) ValidateResults(results []reflect.Value) error { + if len(results) > 0 { + return fmt.Errorf("unexpected return values: %v", results) + } + + var expectTxs []*types.Transaction + err := rlp.DecodeBytes(t.block.Payload, &expectTxs) + if err != nil { + return fmt.Errorf("rlp decode error: %v", err) + } + + app := t.App.(*DexconApp) + blockchain := app.blockchain + block, cachedTxs := app.getConfirmedBlockByHash(t.block.Hash) + if block == nil { + return fmt.Errorf("block can not be nil") + } + + if t.block.Hash != block.Hash { + return fmt.Errorf("block hash not equal %v vs %v", t.block.Hash, block.Hash) + } + + addrInfo := map[common.Address]*addInfo{} + for i, tx := range expectTxs { + if tx.Hash() != cachedTxs[i].Hash() { + return fmt.Errorf("incorrect tx %+v vs %+v", tx, cachedTxs[i]) + } + + msg, err := tx.AsMessage(types.MakeSigner(blockchain.Config(), new(big.Int))) + if err != nil { + panic(err) + } + + nonce := tx.Nonce() + if info, exist := addrInfo[msg.From()]; !exist { + counter := uint64(1) + addrInfo[msg.From()] = &addInfo{nonce: &nonce, cost: tx.Cost(), counter: &counter} + } else { + info.nonce = &nonce + info.cost = new(big.Int).Add(info.cost, tx.Cost()) + } + } + + for addr, info := range addrInfo { + + var expectCost *big.Int + var expectNonce uint64 + var expectCounter uint64 + if t.originAddrInfo[addr].cost == nil { + expectCost = info.cost + } else { + expectCost = new(big.Int).Add(t.originAddrInfo[addr].cost, info.cost) + } + + expectNonce = *info.nonce + + if t.originAddrInfo[addr].counter == nil { + expectCounter = *info.counter + } else { + expectCounter = *t.originAddrInfo[addr].counter + *info.counter + } + + cost, exist := app.addressCost[addr] + counter, exist := app.addressCounter[addr] + nonce, exist := app.addressNonce[addr] + if !exist { + return fmt.Errorf("cache in confirmed block is empty %v %v %v", cost, counter, nonce) + } + + if cost.Cmp(expectCost) != 0 { + return fmt.Errorf("incorrect cost expect %v but %v", expectCost, cost) + } + + if counter != expectCounter { + return fmt.Errorf("incorrect counter expect %v but %v", expectCounter, counter) + } + + if nonce != expectNonce { + return fmt.Errorf("incorrect nonce expect %v but %v", expectNonce, nonce) + } + } + + t.counter++ + t.ready = false + return nil +} + +func (t bcBlockConfirmedTester) StopTime() bool { + return true +} + +func (t *bcBlockConfirmedTester) Rollback() error { + app := t.App.(*DexconApp) + delete(app.confirmedBlocks, t.block.Hash) + app.undeliveredNum-- + for addr, info := range t.originAddrInfo { + if info.nonce == nil { + delete(app.addressNonce, addr) + } else { + app.addressNonce[addr] = *info.nonce + } + + if info.cost == nil { + delete(app.addressCost, addr) + } else { + app.addressCost[addr] = info.cost + } + + if info.cost == nil { + delete(app.addressCounter, addr) + } else { + app.addressCounter[addr] = *info.counter + } + } + + t.originAddrInfo = map[common.Address]addInfo{} + return nil +} + +type BlockDeliveredFactory struct { + FactoryBase +} + +func (f *BlockDeliveredFactory) Run() { + defer func() { + if r := recover(); r != nil { + f.notifyFail(r) + } + }() + + for { + product := f.center.RequestProduct(takerName(f.name)) + + if len(f.testers) > 0 && f.testerAllDone() { + f.notifySuccess() + f.testers = nil + } else if err := f.testerDoWork(product); err != nil { + panic(fmt.Errorf("test fail: %v", err)) + } + + block := f.convertProduct(product) + f.stopTimeMu.RLock() + f.App.BlockDelivered(block.Hash, block.Position, block.Finalization) + f.stopTimeMu.RUnlock() + } +} + +func (f BlockDeliveredFactory) convertProduct(product Product) *coreTypes.Block { + var block *coreTypes.Block + switch product.(type) { + case *BlockConfirmedProduct: + block = &product.(*BlockConfirmedProduct).block + default: + panic(fmt.Errorf("unexpected type %T", product)) + } + + return block +} + +func (f BlockDeliveredFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex) *BlockDeliveredFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + targetFunc: app.BlockDelivered, + status: make(chan map[singnal]interface{}, 1), + stopTimeMu: stopTimeMu, + } + f.center.Register(takerName(f.name), makerName(reflect.TypeOf(BlockConfirmedFactory{}).Name())) + return &f +} + +func (f BlockDeliveredFactory) NewWithTester(app App, center *ProductCenter, + stopTimeMu *sync.RWMutex) *BlockDeliveredFactory { + factory := f.New(app, center, stopTimeMu) + factory.testers = []Tester{ + bdBlockHashTester{}.New(app, 30, 5, 3), + bdBlockDeliveredTester{}.New(app, 60, 5, 3), + } + + return factory +} + +type bdBlockHashTester struct { + baseTester +} + +func (t bdBlockHashTester) New(app App, startAt, interval, threshold int) *bdBlockHashTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *bdBlockHashTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *BlockConfirmedProduct: + t.ready = true + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t bdBlockHashTester) InputsForTest(product Product) []reflect.Value { + block := product.(*BlockConfirmedProduct).block + return []reflect.Value{reflect.ValueOf(coreCommon.Hash{}), reflect.ValueOf(block.Position), + reflect.ValueOf(block.Finalization)} +} + +func (t *bdBlockHashTester) ValidateResults(results []reflect.Value) error { + if len(results) != 1 { + return fmt.Errorf("unexpected return values: %v", results) + } + + switch results[0].Interface().(type) { + case error: + if results[0].Interface().(error).Error() != "Can not get confirmed block" { + return fmt.Errorf("unexpected error: %v", results[0].Interface().(error)) + } + default: + return fmt.Errorf("unexpect results[0] return type %T", results[0].Interface()) + } + + t.counter++ + t.ready = false + return nil +} + +type originalCache struct { + confirmedBlocks map[coreCommon.Hash]*blockInfo + addressNonce map[common.Address]uint64 + addressCost map[common.Address]*big.Int + addressCounter map[common.Address]uint64 +} + +type bdBlockDeliveredTester struct { + baseTester + + expectHeight uint64 + originalCache originalCache + blockInfo *blockInfo +} + +func (t bdBlockDeliveredTester) New(app App, startAt, interval, threshold int) *bdBlockDeliveredTester { + t.baseTester = baseTester{ + App: app, + testTimer: time.NewTimer(time.Duration(startAt) * time.Second), + testInterval: time.Duration(interval) * time.Second, + threshold: threshold, + self: t, + } + return &t +} + +func (t *bdBlockDeliveredTester) ViewAndRecord(product Product) { + select { + case <-t.testTimer.C: + switch product.(type) { + case *BlockConfirmedProduct: + app := t.App.(*DexconApp) + block := product.(*BlockConfirmedProduct).block + t.expectHeight = block.Position.Height + 1 + var txs []*types.Transaction + _, txs = app.getConfirmedBlockByHash(block.Hash) + + if len(txs) > 0 { + t.originalCache.confirmedBlocks = map[coreCommon.Hash]*blockInfo{} + for k, v := range app.confirmedBlocks { + t.originalCache.confirmedBlocks[k] = v + } + + t.originalCache.addressNonce = map[common.Address]uint64{} + for k, v := range app.addressNonce { + t.originalCache.addressNonce[k] = v + } + + t.originalCache.addressCounter = map[common.Address]uint64{} + for k, v := range app.addressCounter { + t.originalCache.addressCounter[k] = v + } + + t.originalCache.addressCost = map[common.Address]*big.Int{} + for k, v := range app.addressCost { + t.originalCache.addressCost[k] = v + } + + t.blockInfo = app.confirmedBlocks[block.Hash] + t.ready = true + } + } + t.testTimer.Reset(t.testInterval) + default: + } +} + +func (t bdBlockDeliveredTester) InputsForTest(product Product) []reflect.Value { + block := product.(*BlockConfirmedProduct).block + return []reflect.Value{reflect.ValueOf(block.Hash), reflect.ValueOf(block.Position), + reflect.ValueOf(block.Finalization)} +} + +func (t *bdBlockDeliveredTester) ValidateResults(results []reflect.Value) error { + if len(results) != 0 { + return fmt.Errorf("unexpected return values: %v", results) + } + + app := t.App.(*DexconApp) + if app.deliveredHeight != t.expectHeight { + return fmt.Errorf("unexpected delivered height: expect %d but %d", t.expectHeight, app.deliveredHeight) + } + + for addr, info := range t.blockInfo.addresses { + if t.originalCache.addressCounter[addr] == 1 { + _, exist := app.addressNonce[addr] + if exist { + return fmt.Errorf("nonce cache %v should not exist", addr) + } + + _, exist = app.addressCost[addr] + if exist { + return fmt.Errorf("cost cache %v should not exist", addr) + } + + _, exist = app.addressCounter[addr] + if exist { + return fmt.Errorf("counter cache %v should not exist", addr) + } + continue + } + + if app.addressNonce[addr] != t.originalCache.addressNonce[addr] { + return fmt.Errorf("nonce should not be affected") + } + + expectCost := new(big.Int).Sub(t.originalCache.addressCost[addr], info.cost) + if expectCost.Cmp(app.addressCost[addr]) != 0 { + return fmt.Errorf("unexpected cost %v %v vs %v", addr, expectCost, app.addressCost[addr]) + } + + if app.addressCounter[addr]+1 != t.originalCache.addressCounter[addr] { + return fmt.Errorf("unexpected counter %v vs %v", app.addressCounter[addr]+1, t.originalCache.addressCounter[addr]) + } + } + + t.counter++ + t.ready = false + return nil +} + +func (t bdBlockDeliveredTester) StopTime() bool { + return true +} + +func (t *bdBlockDeliveredTester) Rollback() error { + app := t.App.(*DexconApp) + block := app.blockchain.CurrentBlock() + app.blockchain.Rollback([]common.Hash{app.blockchain.CurrentBlock().Hash()}) + rawdb.DeleteCanonicalHash(t.App.(*DexconApp).chainDB, block.NumberU64()) + time.Sleep(100 * time.Millisecond) + app.txPool.Reset(app.blockchain.CurrentBlock().Header()) + + app.confirmedBlocks = t.originalCache.confirmedBlocks + app.addressNonce = t.originalCache.addressNonce + app.addressCost = t.originalCache.addressCost + app.addressCounter = t.originalCache.addressCounter + app.undeliveredNum++ + app.deliveredHeight-- + return nil +} + +type TxFactory struct { + FactoryBase + + keys []*ecdsa.PrivateKey + + sendInterval time.Duration + + nonce uint64 +} + +func (f *TxFactory) Run() { + blockchain := f.App.(*DexconApp).blockchain + txPool := f.App.(*DexconApp).txPool + for { + for i, key := range f.keys { + go func(at int, nonce uint64, key *ecdsa.PrivateKey) { + f.stopTimeMu.RLock() + for i := 0; i < len(f.keys); i++ { + if i == at { + continue + } + + tx := types.NewTransaction( + nonce, + crypto.PubkeyToAddress(f.keys[i].PublicKey), + big.NewInt(1), + 21000, + big.NewInt(10e9), + []byte{}) + + signer := types.NewEIP155Signer(blockchain.Config().ChainID) + + tx, err := types.SignTx(tx, signer, key) + if err != nil { + panic(err) + } + + err = txPool.AddLocal(tx) + if err != nil { + panic(err) + } + nonce++ + } + f.stopTimeMu.RUnlock() + }(i, f.nonce, key) + } + + f.nonce += uint64(len(f.keys)) - 1 + + time.Sleep(f.sendInterval) + } +} + +func (f TxFactory) New(app App, center *ProductCenter, stopTimeMu *sync.RWMutex, keys []*ecdsa.PrivateKey) *TxFactory { + f.FactoryBase = FactoryBase{ + App: app, + name: reflect.TypeOf(f).Name(), + center: center, + stopTimeMu: stopTimeMu, + } + f.keys = keys + f.sendInterval = 1000 * time.Millisecond + return &f +} + +func TestDexonApp(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StdoutHandler)) + + masterKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Generate key fail: %v", err) + } + + dex, keys, err := newDexon(masterKey, 15) + if err != nil { + t.Fatalf("New dexon fail: %v", err) + } + + stopTimeMu := &sync.RWMutex{} + + center := ProductCenter{}.New() + configFactory := ConfigFactory{}.New(dex.app, center, stopTimeMu, masterKey) + preparePayloadFactory := PreparePayloadFactory{}.NewWithTester(dex.app, center, stopTimeMu) + prepareWitnessFactory := PrepareWitnessFactory{}.NewWithTester(dex.app, center, stopTimeMu) + verifyBlockFactory := VerifyBlockFactory{}.NewWithTester(dex.app, center, stopTimeMu) + blockConfirmedFactory := BlockConfirmedFactory{}.NewWithTester(dex.app, center, stopTimeMu, masterKey) + blockDeliveredFactory := BlockDeliveredFactory{}.NewWithTester(dex.app, center, stopTimeMu) + txFactory := TxFactory{}.New(dex.app, center, stopTimeMu, keys) + + go configFactory.Run() + go preparePayloadFactory.Run() + go prepareWitnessFactory.Run() + go verifyBlockFactory.Run() + go blockConfirmedFactory.Run() + go blockDeliveredFactory.Run() + go txFactory.Run() + + timer := time.NewTimer(300 * time.Second) + successRecord := make(map[string]struct{}) + for { + select { + case sig := <-preparePayloadFactory.status: + if _, exist := sig[runSuccess]; exist { + successRecord[reflect.TypeOf(*preparePayloadFactory).Name()] = struct{}{} + } else if msg, exist := sig[runFail]; exist { + t.Fatalf("preparePayloadFactory error: %v", msg) + } + case sig := <-prepareWitnessFactory.status: + if _, exist := sig[runSuccess]; exist { + successRecord[reflect.TypeOf(*prepareWitnessFactory).Name()] = struct{}{} + } else if msg, exist := sig[runFail]; exist { + t.Fatalf("prepareWitnessFactory error: %v", msg) + } + case sig := <-verifyBlockFactory.status: + if _, exist := sig[runSuccess]; exist { + successRecord[reflect.TypeOf(*verifyBlockFactory).Name()] = struct{}{} + } else if msg, exist := sig[runFail]; exist { + t.Fatalf("verifyBlockFactory error: %v", msg) + } + case sig := <-blockConfirmedFactory.status: + if _, exist := sig[runSuccess]; exist { + successRecord[reflect.TypeOf(*blockConfirmedFactory).Name()] = struct{}{} + } else if msg, exist := sig[runFail]; exist { + t.Fatalf("blockConfirmedFactory error: %v", msg) + } + case sig := <-blockDeliveredFactory.status: + if _, exist := sig[runSuccess]; exist { + successRecord[reflect.TypeOf(*blockDeliveredFactory).Name()] = struct{}{} + } else if msg, exist := sig[runFail]; exist { + t.Fatalf("blockDeliveredFactory error: %v", msg) + } + case <-timer.C: + t.Fatalf("time's up and all test is not finish yet: %v", successRecord) + } + + leftTesterCount := len(preparePayloadFactory.testers) + len(prepareWitnessFactory.testers) + + len(verifyBlockFactory.testers) + len(blockConfirmedFactory.testers) + len(blockDeliveredFactory.testers) + if leftTesterCount == 0 { + t.Logf("tests all pass") + break + } + + time.Sleep(1 * time.Second) + } +} + +func newDexon(masterKey *ecdsa.PrivateKey, accountNum int) (*Dexon, []*ecdsa.PrivateKey, error) { + db := ethdb.NewMemDatabase() + + genesis := core.DefaultTestnetGenesisBlock() + genesis.Alloc = core.GenesisAlloc{ + crypto.PubkeyToAddress(masterKey.PublicKey): { + Balance: big.NewInt(100000000000000000), + Staked: big.NewInt(50000000000000000), + PublicKey: crypto.FromECDSAPub(&masterKey.PublicKey), + }, + } + + var accounts []*ecdsa.PrivateKey + for i := 0; i < accountNum; i++ { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + + genesis.Alloc[crypto.PubkeyToAddress(key.PublicKey)] = core.GenesisAccount{ + Balance: math.BigPow(10, 18), + Staked: big.NewInt(0), + } + accounts = append(accounts, key) + } + + genesis.Config.Dexcon.BlockGasLimit = 2000000 + genesis.Config.Dexcon.RoundLength = 60 + genesis.Config.Dexcon.Owner = crypto.PubkeyToAddress(masterKey.PublicKey) + + chainConfig, _, err := core.SetupGenesisBlock(db, genesis) + if err != nil { + return nil, nil, err + } + + config := Config{PrivateKey: masterKey} + vmConfig := vm.Config{IsBlockProposer: true} + + engine := dexcon.New() + + dex := &Dexon{ + chainDb: db, + chainConfig: chainConfig, + networkID: config.NetworkId, + engine: engine, + } + + dex.blockchain, err = core.NewBlockChain(db, nil, chainConfig, engine, vmConfig, nil) + if err != nil { + return nil, nil, err + } + + txPoolConfig := core.DefaultTxPoolConfig + dex.txPool = core.NewTxPool(txPoolConfig, chainConfig, dex.blockchain) + + dex.APIBackend = &DexAPIBackend{dex, nil} + dex.governance = NewDexconGovernance(dex.APIBackend, dex.chainConfig, config.PrivateKey) + engine.SetGovStateFetcher(dex.governance) + dex.app = NewDexconApp(dex.txPool, dex.blockchain, dex.governance, db, &config) + + return dex, accounts, nil +} |