aboutsummaryrefslogtreecommitdiffstats
path: root/dex
diff options
context:
space:
mode:
authorbojie <bojie@dexon.org>2019-01-07 15:40:56 +0800
committerWei-Ning Huang <w@byzantine-lab.io>2019-06-12 17:27:21 +0800
commitbc788bdf15656f8e867cf9a81769fb280b5bfb49 (patch)
treebd8ef519f56308878520bb7a5589dbdcb3970bfa /dex
parent4ca846ead9a66792a6b6b0e1ab8323b2f056b6f3 (diff)
downloadgo-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.go56
-rw-r--r--dex/app_test.go207
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 {