diff options
author | bojie <bojie@dexon.org> | 2019-01-07 15:40:56 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@byzantine-lab.io> | 2019-06-12 17:27:21 +0800 |
commit | bc788bdf15656f8e867cf9a81769fb280b5bfb49 (patch) | |
tree | bd8ef519f56308878520bb7a5589dbdcb3970bfa /dex | |
parent | 4ca846ead9a66792a6b6b0e1ab8323b2f056b6f3 (diff) | |
download | go-tangerine-bc788bdf15656f8e867cf9a81769fb280b5bfb49.tar.gz go-tangerine-bc788bdf15656f8e867cf9a81769fb280b5bfb49.tar.zst go-tangerine-bc788bdf15656f8e867cf9a81769fb280b5bfb49.zip |
app: implement logic for prepare/verify correctly when chain number change (#118)
Diffstat (limited to 'dex')
-rw-r--r-- | dex/app.go | 56 | ||||
-rw-r--r-- | dex/app_test.go | 207 |
2 files changed, 257 insertions, 6 deletions
diff --git a/dex/app.go b/dex/app.go index c52a6c79b..b1a0b4548 100644 --- a/dex/app.go +++ b/dex/app.go @@ -29,6 +29,7 @@ import ( "github.com/dexon-foundation/dexon/common" "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/ethdb" "github.com/dexon-foundation/dexon/event" @@ -175,6 +176,22 @@ func (d *DexconApp) preparePayload(ctx context.Context, position coreTypes.Posit default: } + if position.Round > 0 { + // If round chain number changed but new round is not delivered yet, payload must be nil. + previousNumChains := d.gov.Configuration(position.Round - 1).NumChains + currentNumChains := d.gov.Configuration(position.Round).NumChains + if previousNumChains != currentNumChains { + deliveredRound, err := rawdb.ReadLastRoundNumber(d.chainDB) + if err != nil { + panic(fmt.Errorf("read current round error: %v", err)) + } + + if deliveredRound < position.Round { + return nil, nil + } + } + } + if position.Height != 0 { // Check if chain block height is strictly increamental. chainLastHeight, ok := d.blockchain.GetChainLastConfirmedHeight(position.ChainID) @@ -186,8 +203,7 @@ func (d *DexconApp) preparePayload(ctx context.Context, position coreTypes.Posit } b, latestState := d.blockchain.GetPending() - log.Debug("Prepare payload", "chain", position.ChainID, "height", position.Height, "state", - b.Root().String()) + log.Debug("Prepare payload", "chain", position.ChainID, "height", position.Height, "state", b.Root().String()) txsMap, err := d.txPool.Pending() if err != nil { @@ -345,6 +361,26 @@ func (d *DexconApp) VerifyBlock(block *coreTypes.Block) coreTypes.BlockVerifySta } } + if block.Position.Round > 0 { + // If round chain number changed but new round is not delivered yet, payload must be nil. + previousNumChains := d.gov.Configuration(block.Position.Round - 1).NumChains + currentNumChains := d.gov.Configuration(block.Position.Round).NumChains + if previousNumChains != currentNumChains { + deliveredRound, err := rawdb.ReadLastRoundNumber(d.chainDB) + if err != nil { + panic(fmt.Errorf("read current round error: %v", err)) + } + + if deliveredRound < block.Position.Round { + if len(block.Payload) > 0 { + return coreTypes.VerifyInvalidBlock + } + + return coreTypes.VerifyOK + } + } + } + // Get latest pending state. b, latestState := d.blockchain.GetPending() log.Debug("Verify block", "chain", block.Position.ChainID, "height", block.Position.Height, "state", @@ -482,10 +518,18 @@ func (d *DexconApp) BlockDelivered( }, txs, nil, nil) h := d.blockchain.CurrentBlock().NumberU64() + 1 - _, err = d.blockchain.ProcessPendingBlock(newBlock, &block.Witness) - if err != nil { - log.Error("Failed to process pending block", "error", err) - panic(err) + if block.IsEmpty() { + err = d.blockchain.ProcessEmptyBlock(newBlock) + if err != nil { + log.Error("Failed to process empty block", "error", err) + panic(err) + } + } else { + _, err = d.blockchain.ProcessPendingBlock(newBlock, &block.Witness) + if err != nil { + log.Error("Failed to process pending block", "error", err) + panic(err) + } } d.blockchain.RemoveConfirmedBlock(chainID, blockHash) diff --git a/dex/app_test.go b/dex/app_test.go index 7fc4933de..f4cc2fd9a 100644 --- a/dex/app_test.go +++ b/dex/app_test.go @@ -4,12 +4,14 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "strings" "testing" "time" coreCommon "github.com/dexon-foundation/dexon-consensus/common" coreTypes "github.com/dexon-foundation/dexon-consensus/core/types" + "github.com/dexon-foundation/dexon/accounts/abi" "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/consensus/dexcon" "github.com/dexon-foundation/dexon/core" @@ -158,6 +160,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 100) if err != nil { t.Errorf("prepare data error: %v", err) @@ -174,6 +177,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 1, 100) if err != nil { t.Errorf("prepare data error: %v", err) @@ -190,6 +194,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 2 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 100) if err != nil { t.Errorf("prepare data error: %v", err) @@ -206,6 +211,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 10000) if err != nil { t.Errorf("prepare data error: %v", err) @@ -222,6 +228,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0) if err != nil { t.Errorf("prepare data error: %v", err) @@ -256,6 +263,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0) if err != nil { t.Errorf("prepare data error: %v", err) @@ -290,6 +298,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0) if err != nil { t.Errorf("prepare data error: %v", err) @@ -368,6 +377,7 @@ func TestBlockConfirmed(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Witness = witness block.Payload = payload + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Position.ChainID = uint32(chainID.Uint64()) dex.app.BlockConfirmed(*block) @@ -463,6 +473,147 @@ func TestBlockDelivered(t *testing.T) { } } +func TestNumChainsChange(t *testing.T) { + key, err := crypto.GenerateKey() + if err != nil { + t.Errorf("hex to ecdsa error: %v", err) + } + + params.TestnetChainConfig.Dexcon.Owner = crypto.PubkeyToAddress(key.PublicKey) + + dex, err := newTestDexonWithGenesis(key) + if err != nil { + t.Errorf("new test dexon error: %v", err) + } + + abiObject, err := abi.JSON(strings.NewReader(vm.GovernanceABIJSON)) + if err != nil { + t.Errorf("get abi object fail: %v", err) + } + + // Update config in round 1 and height 1. + // Config will affect in round 3. + input, err := abiObject.Pack("updateConfiguration", + new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e4)), big.NewInt(2000), + big.NewInt(1e17), big.NewInt(9000000), big.NewInt(3), big.NewInt(500), big.NewInt(5000), + big.NewInt(1), big.NewInt(700000), big.NewInt(5), big.NewInt(5), big.NewInt(700000), big.NewInt(1000), + []*big.Int{big.NewInt(1), big.NewInt(1), big.NewInt(1)}) + if err != nil { + t.Errorf("updateConfiguration abiObject pack error: %v", err) + } + + block, err := prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{input}, 1) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 1, + }) + + // Snapshot round on round 1 and height 2. + input, err = abiObject.Pack("snapshotRound", big.NewInt(1), big.NewInt(1)) + if err != nil { + t.Errorf("abiObject pack error: %v", err) + } + + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{input}, 1) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 2, + }) + + // Make round 1 height 2 block write into chain. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, nil, 1) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 3, + }) + + // Round 2 will keep prepare tx as usual. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 2) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload == nil { + t.Errorf("payload should not be nil") + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 4, + }) + + // It will prepare empty payload until witness any block in round 3. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 3) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload != nil { + t.Errorf("payload should be nil but %v", block.Payload) + } + + // Test non-empty payload. + block.Payload = []byte{1} + if status := dex.app.VerifyBlock(block); status != coreTypes.VerifyInvalidBlock { + t.Errorf("unexpected verify status %v", status) + } + block.Payload = nil + + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 5, + }) + + // Still empty payload because round 3 is not witness yet. + // This block just for witness round 3 height 5. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 3) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload != nil { + t.Errorf("payload should be nil but %v", block.Payload) + } + + // Test non-empty payload. + block.Payload = []byte{1} + if status := dex.app.VerifyBlock(block); status != coreTypes.VerifyInvalidBlock { + t.Errorf("unexpected verify status %v", status) + } + block.Payload = nil + + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 6, + }) + + // Empty block in round 3 has been delivered. + // Now can prepare payload as usual. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 3) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload == nil { + t.Errorf("payload should not be nil") + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 7, + }) +} + func BenchmarkBlockDeliveredFlow(b *testing.B) { key, err := crypto.GenerateKey() if err != nil { @@ -594,6 +745,61 @@ func prepareData(dex *Dexon, key *ecdsa.PrivateKey, startNonce, txNum int) ( return } +func prepareConfirmedBlockWithTxAndData(dex *Dexon, key *ecdsa.PrivateKey, data [][]byte, round uint64) ( + Block *coreTypes.Block, err error) { + address := crypto.PubkeyToAddress(key.PublicKey) + numChains := dex.governance.GetConfigHelper(round).Configuration().NumChains + chainID := new(big.Int).Mod(address.Big(), big.NewInt(int64(numChains))) + + for _, d := range data { + // Prepare one block for pending. + nonce := dex.txPool.State().GetNonce(address) + signer := types.NewEIP155Signer(dex.chainConfig.ChainID) + tx := types.NewTransaction(uint64(nonce), vm.GovernanceContractAddress, big.NewInt(0), params.TxGas*2, + big.NewInt(1), d) + tx, err = types.SignTx(tx, signer, key) + if err != nil { + return nil, err + } + + dex.txPool.AddRemote(tx) + if err != nil { + return nil, err + } + } + var ( + payload []byte + witness coreTypes.Witness + ) + + payload, err = dex.app.PreparePayload(coreTypes.Position{Round: round, ChainID: uint32(chainID.Uint64())}) + if err != nil { + return + } + + witness, err = dex.app.PrepareWitness(0) + if err != nil { + return nil, err + } + + block := &coreTypes.Block{} + block.Hash = coreCommon.NewRandomHash() + block.Witness = witness + block.Payload = payload + block.Position.ChainID = uint32(chainID.Uint64()) + block.Position.Round = round + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} + + status := dex.app.VerifyBlock(block) + if status != coreTypes.VerifyOK { + err = fmt.Errorf("verify fail: %v", status) + return nil, err + } + + dex.app.BlockConfirmed(*block) + return block, nil +} + func prepareDataWithoutTxPool(dex *Dexon, key *ecdsa.PrivateKey, startNonce, txNum int) ( payload []byte, witness coreTypes.Witness, err error) { signer := types.NewEIP155Signer(dex.chainConfig.ChainID) @@ -656,6 +862,7 @@ func prepareConfirmedBlocks(dex *Dexon, keys []*ecdsa.PrivateKey, txNum int) (bl block.Witness = witness block.Payload = payload block.Position.ChainID = uint32(chainID.Uint64()) + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} status := dex.app.VerifyBlock(block) if status != coreTypes.VerifyOK { |