diff options
author | Mission Liao <mission.liao@dexon.org> | 2018-12-18 10:02:30 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-03-12 12:19:09 +0800 |
commit | 9b24bf9dc17fa8d1fcf658cd2d7a7b4754e65233 (patch) | |
tree | 62340aff3a722cd2a8ccbf36193839f1cc26b8e9 | |
parent | c072ba693976104141caa2bb0828cc1230c4bc00 (diff) | |
download | dexon-9b24bf9dc17fa8d1fcf658cd2d7a7b4754e65233.tar.gz dexon-9b24bf9dc17fa8d1fcf658cd2d7a7b4754e65233.tar.zst dexon-9b24bf9dc17fa8d1fcf658cd2d7a7b4754e65233.zip |
vendor: sync to latest core (#91)
- Implement new methods in db to cache DKG
private key.
- Implement new methods in db to cache compaction
chain tip.
-rw-r--r-- | core/rawdb/accessors_core_chain_tip.go | 49 | ||||
-rw-r--r-- | core/rawdb/accessors_core_dkg_private_key.go | 48 | ||||
-rw-r--r-- | core/rawdb/schema.go | 12 | ||||
-rw-r--r-- | dex/backend.go | 4 | ||||
-rw-r--r-- | dex/db/db.go (renamed from dex/blockdb/db.go) | 58 | ||||
-rw-r--r-- | dex/handler.go | 4 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go | 109 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go | 25 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/utils.go | 21 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go | 23 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go | 176 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go | 92 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go | 13 | ||||
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go | 4 | ||||
-rw-r--r-- | vendor/vendor.json | 42 |
15 files changed, 563 insertions, 117 deletions
diff --git a/core/rawdb/accessors_core_chain_tip.go b/core/rawdb/accessors_core_chain_tip.go new file mode 100644 index 000000000..95fa8854f --- /dev/null +++ b/core/rawdb/accessors_core_chain_tip.go @@ -0,0 +1,49 @@ +package rawdb + +import ( + "bytes" + + coreCommon "github.com/dexon-foundation/dexon-consensus/common" + "github.com/dexon-foundation/dexon/log" + "github.com/dexon-foundation/dexon/rlp" +) + +func ReadCoreCompactionChainTipRLP(db DatabaseReader) (rlp.RawValue, error) { + return db.Get(coreCompactionChainTipKey) +} + +func WriteCoreCompactionChainTipRLP(db DatabaseWriter, rlp rlp.RawValue) error { + if err := db.Put(coreCompactionChainTipKey, rlp); err != nil { + log.Crit("Failed to store core compaction chain tip") + return err + } + return nil +} + +func ReadCoreCompactionChainTip(db DatabaseReader) (coreCommon.Hash, uint64) { + data, err := ReadCoreCompactionChainTipRLP(db) + if err != nil { + return coreCommon.Hash{}, 0 + } + v := struct { + Height uint64 + Hash coreCommon.Hash + }{} + if err := rlp.Decode(bytes.NewReader(data), &v); err != nil { + log.Error("Invalid core compaction chain tip RLP", "err", err) + return coreCommon.Hash{}, 0 + } + return v.Hash, v.Height +} + +func WriteCoreCompactionChainTip(db DatabaseWriter, hash coreCommon.Hash, height uint64) error { + data, err := rlp.EncodeToBytes(&struct { + Height uint64 + Hash coreCommon.Hash + }{height, hash}) + if err != nil { + log.Crit("Failed to RLP encode core compaction chain tip", "err", err) + return err + } + return WriteCoreCompactionChainTipRLP(db, data) +} diff --git a/core/rawdb/accessors_core_dkg_private_key.go b/core/rawdb/accessors_core_dkg_private_key.go new file mode 100644 index 000000000..ac51ca5ec --- /dev/null +++ b/core/rawdb/accessors_core_dkg_private_key.go @@ -0,0 +1,48 @@ +package rawdb + +import ( + "bytes" + + coreDKG "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg" + "github.com/dexon-foundation/dexon/log" + "github.com/dexon-foundation/dexon/rlp" +) + +func ReadCoreDKGPrivateKeyRLP(db DatabaseReader, round uint64) rlp.RawValue { + data, _ := db.Get(coreDKGPrivateKeyKey(round)) + return data +} + +func WriteCoreDKGPrivateKeyRLP(db DatabaseWriter, round uint64, rlp rlp.RawValue) error { + err := db.Put(coreDKGPrivateKeyKey(round), rlp) + if err != nil { + log.Crit("Failed to store core DKG private key", "err", err, "round", round) + } + return err +} + +func HasCoreDKGPrivateKey(db DatabaseReader, round uint64) (bool, error) { + return db.Has(coreDKGPrivateKeyKey(round)) +} + +func ReadCoreDKGPrivateKey(db DatabaseReader, round uint64) *coreDKG.PrivateKey { + data := ReadCoreDKGPrivateKeyRLP(db, round) + if len(data) == 0 { + return nil + } + key := new(coreDKG.PrivateKey) + if err := rlp.Decode(bytes.NewReader(data), key); err != nil { + log.Error("Invalid core DKG private key RLP", "round", round, "err", err) + return nil + } + return key +} + +func WriteCoreDKGPrivateKey(db DatabaseWriter, round uint64, key *coreDKG.PrivateKey) error { + data, err := rlp.EncodeToBytes(key) + if err != nil { + log.Crit("Failed to RLP encode core DKG private key", "round", round, "err", err) + return err + } + return WriteCoreDKGPrivateKeyRLP(db, round, data) +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 9a820a578..87a56c0ba 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -53,7 +53,9 @@ var ( txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits - coreBlockPrefix = []byte("D") + coreBlockPrefix = []byte("D") + coreDKGPrivateKeyPrefix = []byte("DPK") + coreCompactionChainTipKey = []byte("CoreChainTip") preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -120,6 +122,14 @@ func coreBlockKey(hash common.Hash) []byte { return append(coreBlockPrefix, hash.Bytes()...) } +// coreDKGPrivateKeyKey = coreDKGPrivateKeyPrefix + round +func coreDKGPrivateKeyKey(round uint64) []byte { + ret := make([]byte, len(coreDKGPrivateKeyPrefix)+8) + copy(ret, coreDKGPrivateKeyPrefix) + binary.LittleEndian.PutUint64(ret[len(coreDKGPrivateKeyPrefix):], round) + return ret +} + // bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash func bloomBitsKey(bit uint, section uint64, hash common.Hash) []byte { key := append(append(bloomBitsPrefix, make([]byte, 10)...), hash.Bytes()...) diff --git a/dex/backend.go b/dex/backend.go index 5ea30b1a9..2c87425de 100644 --- a/dex/backend.go +++ b/dex/backend.go @@ -31,7 +31,7 @@ import ( "github.com/dexon-foundation/dexon/core/bloombits" "github.com/dexon-foundation/dexon/core/rawdb" "github.com/dexon-foundation/dexon/core/vm" - "github.com/dexon-foundation/dexon/dex/blockdb" + dexDB "github.com/dexon-foundation/dexon/dex/db" "github.com/dexon-foundation/dexon/dex/downloader" "github.com/dexon-foundation/dexon/eth/filters" "github.com/dexon-foundation/dexon/eth/gasprice" @@ -170,7 +170,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Dexon, error) { log.Info("DEXON Consensus DMoment", "time", dMoment) dex.consensus = dexCore.NewConsensus(dMoment, - dex.app, dex.governance, blockdb.NewDatabase(chainDb), dex.network, privKey, log.Root()) + dex.app, dex.governance, dexDB.NewDatabase(chainDb), dex.network, privKey, log.Root()) return dex, nil } diff --git a/dex/blockdb/db.go b/dex/db/db.go index 4f08a3edd..d51c2df54 100644 --- a/dex/blockdb/db.go +++ b/dex/db/db.go @@ -15,10 +15,11 @@ // along with the dexon-consensus library. If not, see // <http://www.gnu.org/licenses/>. -package blockdb +package db import ( coreCommon "github.com/dexon-foundation/dexon-consensus/common" + coreDKG "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg" coreDb "github.com/dexon-foundation/dexon-consensus/core/db" coreTypes "github.com/dexon-foundation/dexon-consensus/core/types" @@ -27,20 +28,20 @@ import ( "github.com/dexon-foundation/dexon/ethdb" ) -// BlockDB implement dexon-consensus BlockDatabase interface. -type BlockDB struct { +// DB implement dexon-consensus BlockDatabase interface. +type DB struct { db ethdb.Database } -func NewDatabase(db ethdb.Database) *BlockDB { - return &BlockDB{db} +func NewDatabase(db ethdb.Database) *DB { + return &DB{db} } -func (d *BlockDB) HasBlock(hash coreCommon.Hash) bool { +func (d *DB) HasBlock(hash coreCommon.Hash) bool { return rawdb.HasCoreBlock(d.db, common.Hash(hash)) } -func (d *BlockDB) GetBlock(hash coreCommon.Hash) (coreTypes.Block, error) { +func (d *DB) GetBlock(hash coreCommon.Hash) (coreTypes.Block, error) { block := rawdb.ReadCoreBlock(d.db, common.Hash(hash)) if block == nil { return coreTypes.Block{}, coreDb.ErrBlockDoesNotExist @@ -48,11 +49,11 @@ func (d *BlockDB) GetBlock(hash coreCommon.Hash) (coreTypes.Block, error) { return *block, nil } -func (d *BlockDB) GetAllBlocks() (coreDb.BlockIterator, error) { +func (d *DB) GetAllBlocks() (coreDb.BlockIterator, error) { return nil, coreDb.ErrNotImplemented } -func (d *BlockDB) UpdateBlock(block coreTypes.Block) error { +func (d *DB) UpdateBlock(block coreTypes.Block) error { if !d.HasBlock(block.Hash) { return coreDb.ErrBlockDoesNotExist } @@ -60,7 +61,7 @@ func (d *BlockDB) UpdateBlock(block coreTypes.Block) error { return nil } -func (d *BlockDB) PutBlock(block coreTypes.Block) error { +func (d *DB) PutBlock(block coreTypes.Block) error { if d.HasBlock(block.Hash) { return coreDb.ErrBlockExists } @@ -68,4 +69,39 @@ func (d *BlockDB) PutBlock(block coreTypes.Block) error { return nil } -func (d *BlockDB) Close() error { return nil } +func (d *DB) HasDKGPrivateKey(round uint64) (bool, error) { + return rawdb.HasCoreDKGPrivateKey(d.db, round) +} + +func (d *DB) GetDKGPrivateKey(round uint64) (coreDKG.PrivateKey, error) { + key := rawdb.ReadCoreDKGPrivateKey(d.db, round) + if key == nil { + return coreDKG.PrivateKey{}, coreDb.ErrDKGPrivateKeyDoesNotExist + } + return *key, nil +} + +func (d *DB) PutDKGPrivateKey(round uint64, key coreDKG.PrivateKey) error { + has, err := d.HasDKGPrivateKey(round) + if err != nil { + return err + } + if has { + return coreDb.ErrDKGPrivateKeyExists + } + return rawdb.WriteCoreDKGPrivateKey(d.db, round, &key) +} + +func (d *DB) PutCompactionChainTipInfo(hash coreCommon.Hash, height uint64) error { + _, currentHeight := d.GetCompactionChainTipInfo() + if height <= currentHeight { + return coreDb.ErrInvalidCompactionChainTipHeight + } + return rawdb.WriteCoreCompactionChainTip(d.db, hash, height) +} + +func (d *DB) GetCompactionChainTipInfo() (hash coreCommon.Hash, height uint64) { + return rawdb.ReadCoreCompactionChainTip(d.db) +} + +func (d *DB) Close() error { return nil } diff --git a/dex/handler.go b/dex/handler.go index 620320f49..9174d8516 100644 --- a/dex/handler.go +++ b/dex/handler.go @@ -53,7 +53,7 @@ import ( "github.com/dexon-foundation/dexon/core" "github.com/dexon-foundation/dexon/core/types" "github.com/dexon-foundation/dexon/crypto" - "github.com/dexon-foundation/dexon/dex/blockdb" + dexDB "github.com/dexon-foundation/dexon/dex/db" "github.com/dexon-foundation/dexon/dex/downloader" "github.com/dexon-foundation/dexon/dex/fetcher" "github.com/dexon-foundation/dexon/ethdb" @@ -158,7 +158,7 @@ func NewProtocolManager( nodeTable: tab, gov: gov, blockchain: blockchain, - cache: newCache(5120, blockdb.NewDatabase(chaindb)), + cache: newCache(5120, dexDB.NewDatabase(chaindb)), chainconfig: config, newPeerCh: make(chan *peer), noMorePeers: make(chan struct{}), diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go index 364f2c75c..2b3a859ed 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go @@ -24,6 +24,7 @@ import ( "github.com/dexon-foundation/dexon-consensus/common" "github.com/dexon-foundation/dexon-consensus/core/crypto" + "github.com/dexon-foundation/dexon-consensus/core/db" "github.com/dexon-foundation/dexon-consensus/core/types" typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg" "github.com/dexon-foundation/dexon-consensus/core/utils" @@ -53,6 +54,7 @@ type configurationChain struct { tsigTouched map[common.Hash]struct{} tsigReady *sync.Cond cache *utils.NodeSetCache + db db.Database dkgSet map[types.NodeID]struct{} mpkReady bool pendingPrvShare map[types.NodeID]*typesDKG.PrivateShare @@ -66,6 +68,7 @@ func newConfigurationChain( recv dkgReceiver, gov Governance, cache *utils.NodeSetCache, + dbInst db.Database, logger common.Logger) *configurationChain { return &configurationChain{ ID: ID, @@ -78,6 +81,7 @@ func newConfigurationChain( tsigTouched: make(map[common.Hash]struct{}), tsigReady: sync.NewCond(&sync.Mutex{}), cache: cache, + db: dbInst, pendingPsig: make(map[common.Hash][]*typesDKG.PartialSignature), } } @@ -104,6 +108,10 @@ func (cc *configurationChain) registerDKG(round uint64, threshold int) { } func (cc *configurationChain) runDKG(round uint64) error { + // Check if corresponding DKG signer is ready. + if _, _, err := cc.getDKGInfo(round); err == nil { + return nil + } cc.dkgLock.Lock() defer cc.dkgLock.Unlock() if cc.dkg == nil || cc.dkg.round != round { @@ -113,20 +121,11 @@ func (cc *configurationChain) runDKG(round uint64) error { } return ErrDKGNotRegistered } - if func() bool { - cc.dkgResult.RLock() - defer cc.dkgResult.RUnlock() - _, exist := cc.gpk[round] - return exist - }() { - return nil - } cc.logger.Debug("Calling Governance.IsDKGFinal", "round", round) if cc.gov.IsDKGFinal(round) { cc.logger.Warn("DKG already final", "round", round) return nil } - ticker := newTicker(cc.gov, round, TickerDKG) cc.dkgLock.Unlock() <-ticker.Tick() @@ -153,7 +152,8 @@ func (cc *configurationChain) runDKG(round uint64) error { cc.dkgLock.Lock() // Phase 5(T = 2λ): Propose Anti nack complaint. cc.logger.Debug("Calling Governance.DKGComplaints", "round", round) - cc.dkg.processNackComplaints(cc.gov.DKGComplaints(round)) + complaints := cc.gov.DKGComplaints(round) + cc.dkg.processNackComplaints(complaints) cc.dkgLock.Unlock() <-ticker.Tick() cc.dkgLock.Lock() @@ -163,8 +163,7 @@ func (cc *configurationChain) runDKG(round uint64) error { <-ticker.Tick() cc.dkgLock.Lock() // Phase 7(T = 4λ): Enforce complaints and nack complaints. - cc.logger.Debug("Calling Governance.DKGComplaints", "round", round) - cc.dkg.enforceNackComplaints(cc.gov.DKGComplaints(round)) + cc.dkg.enforceNackComplaints(complaints) // Enforce complaint is done in `processPrivateShare`. // Phase 8(T = 5λ): DKG finalize. cc.dkgLock.Unlock() @@ -209,6 +208,10 @@ func (cc *configurationChain) runDKG(round uint64) error { if err != nil { return err } + // Save private shares to DB. + if err = cc.db.PutDKGPrivateKey(round, *signer.privateKey); err != nil { + return err + } cc.dkgResult.Lock() defer cc.dkgResult.Unlock() cc.dkgSigner[round] = signer @@ -220,23 +223,74 @@ func (cc *configurationChain) isDKGReady(round uint64) bool { if !cc.gov.IsDKGFinal(round) { return false } - return func() bool { + _, _, err := cc.getDKGInfo(round) + return err == nil +} + +func (cc *configurationChain) getDKGInfo( + round uint64) (*DKGGroupPublicKey, *dkgShareSecret, error) { + getFromCache := func() (*DKGGroupPublicKey, *dkgShareSecret) { cc.dkgResult.RLock() defer cc.dkgResult.RUnlock() - _, exist := cc.gpk[round] - return exist - }() + gpk := cc.gpk[round] + signer := cc.dkgSigner[round] + return gpk, signer + } + gpk, signer := getFromCache() + if gpk == nil || signer == nil { + if err := cc.recoverDKGInfo(round); err != nil { + return nil, nil, err + } + gpk, signer = getFromCache() + } + if gpk == nil || signer == nil { + return nil, nil, ErrDKGNotReady + } + return gpk, signer, nil +} + +func (cc *configurationChain) recoverDKGInfo(round uint64) error { + cc.dkgResult.Lock() + defer cc.dkgResult.Unlock() + _, signerExists := cc.dkgSigner[round] + _, gpkExists := cc.gpk[round] + if signerExists && gpkExists { + return nil + } + if !cc.gov.IsDKGFinal(round) { + return ErrDKGNotReady + } + + threshold := getDKGThreshold(cc.gov.Configuration(round)) + // Restore group public key. + gpk, err := NewDKGGroupPublicKey(round, + cc.gov.DKGMasterPublicKeys(round), + cc.gov.DKGComplaints(round), + threshold) + if err != nil { + return err + } + // Restore DKG share secret, this segment of code is copied from + // dkgProtocol.recoverShareSecret. + if len(gpk.qualifyIDs) < threshold { + return ErrNotReachThreshold + } + // Check if we have private shares in DB. + prvKey, err := cc.db.GetDKGPrivateKey(round) + if err != nil { + return err + } + cc.gpk[round] = gpk + cc.dkgSigner[round] = &dkgShareSecret{ + privateKey: &prvKey, + } + return nil } func (cc *configurationChain) preparePartialSignature( round uint64, hash common.Hash) (*typesDKG.PartialSignature, error) { - signer, exist := func() (*dkgShareSecret, bool) { - cc.dkgResult.RLock() - defer cc.dkgResult.RUnlock() - signer, exist := cc.dkgSigner[round] - return signer, exist - }() - if !exist { + _, signer, _ := cc.getDKGInfo(round) + if signer == nil { return nil, ErrDKGNotReady } return &typesDKG.PartialSignature{ @@ -264,13 +318,8 @@ func (cc *configurationChain) untouchTSigHash(hash common.Hash) { func (cc *configurationChain) runTSig( round uint64, hash common.Hash) ( crypto.Signature, error) { - gpk, exist := func() (*DKGGroupPublicKey, bool) { - cc.dkgResult.RLock() - defer cc.dkgResult.RUnlock() - gpk, exist := cc.gpk[round] - return gpk, exist - }() - if !exist { + gpk, _, _ := cc.getDKGInfo(round) + if gpk == nil { return crypto.Signature{}, ErrDKGNotReady } cc.tsigReady.L.Lock() diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go index a6d80371d..5beaf546a 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go @@ -424,6 +424,7 @@ func NewConsensus( recv, gov, nodeSetCache, + db, logger) recv.cfgModule = cfgModule // Construct Consensus instance. @@ -490,6 +491,7 @@ func NewConsensusFromSyncer( recv, gov, nodeSetCache, + db, logger) recv.cfgModule = cfgModule // Setup Consensus instance. @@ -576,7 +578,7 @@ func (con *Consensus) prepare(initBlock *types.Block) error { } if _, exist := dkgSet[con.ID]; exist { con.logger.Info("Selected as DKG set", "round", initRound) - con.cfgModule.registerDKG(initRound, int(initConfig.DKGSetSize)/3+1) + con.cfgModule.registerDKG(initRound, getDKGThreshold(initConfig)) con.event.RegisterTime(con.dMoment.Add(initConfig.RoundInterval/4), func(time.Time) { con.runDKG(initRound, initConfig) @@ -742,8 +744,7 @@ func (con *Consensus) initialRound( return } con.logger.Info("Selected as DKG set", "round", nextRound) - con.cfgModule.registerDKG( - nextRound, int(config.DKGSetSize/3)+1) + con.cfgModule.registerDKG(nextRound, getDKGThreshold(config)) con.event.RegisterTime( startTime.Add(config.RoundInterval*2/3), func(time.Time) { @@ -1003,6 +1004,14 @@ func (con *Consensus) preProcessBlock(b *types.Block) (err error) { // deliverBlock deliver a block to application layer. func (con *Consensus) deliverBlock(b *types.Block) { + if err := con.db.UpdateBlock(*b); err != nil { + panic(err) + } + if err := con.db.PutCompactionChainTipInfo( + b.Hash, b.Finalization.Height); err != nil { + panic(err) + } + con.cfgModule.untouchTSigHash(b.Hash) con.logger.Debug("Calling Application.BlockDelivered", "block", b) con.app.BlockDelivered(b.Hash, b.Position, b.Finalization.Clone()) if b.Position.Round == con.roundToNotify { @@ -1032,6 +1041,9 @@ func (con *Consensus) deliverBlock(b *types.Block) { con.roundToNotify, b.Finalization.Height) con.roundToNotify++ } + if con.debugApp != nil { + con.debugApp.BlockReady(b.Hash) + } } // processBlock is the entry point to submit one block to a Consensus instance. @@ -1072,14 +1084,7 @@ func (con *Consensus) processBlock(block *types.Block) (err error) { "delivered", con.ccModule.lastDeliveredBlock(), "pending", con.ccModule.lastPendingBlock()) for _, b := range deliveredBlocks { - if err = con.db.UpdateBlock(*b); err != nil { - panic(err) - } - con.cfgModule.untouchTSigHash(b.Hash) con.deliverBlock(b) - if con.debugApp != nil { - con.debugApp.BlockReady(b.Hash) - } } if err = con.lattice.PurgeBlocks(deliveredBlocks); err != nil { return diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/utils.go index fa4ad9f05..9e470f0cf 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/utils.go @@ -18,7 +18,9 @@ package dkg import ( + "encoding/binary" "fmt" + "math/rand" "github.com/dexon-foundation/bls/ffi/go/bls" @@ -69,3 +71,22 @@ func RecoverGroupPublicKey(pubShares []*PublicKeyShares) *PublicKey { } return pub } + +// NewRandomPrivateKeyShares constructs a private key shares randomly. +func NewRandomPrivateKeyShares() *PrivateKeyShares { + // Generate IDs. + rndIDs := make(IDs, 0, 10) + for i := range rndIDs { + id := make([]byte, 8) + binary.LittleEndian.PutUint64(id, rand.Uint64()) + rndIDs[i] = NewID(id) + } + prvShares := NewEmptyPrivateKeyShares() + prvShares.SetParticipants(rndIDs) + for _, id := range rndIDs { + if err := prvShares.AddShare(id, NewPrivateKey()); err != nil { + panic(err) + } + } + return prvShares +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go index 5e13dc604..ebbbbd475 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/dexon-foundation/dexon-consensus/common" + "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg" "github.com/dexon-foundation/dexon-consensus/core/types" ) @@ -38,6 +39,17 @@ var ( ErrClosed = fmt.Errorf("db closed") // ErrNotImplemented is the error that some interface is not implemented. ErrNotImplemented = fmt.Errorf("not implemented") + // ErrInvalidCompactionChainTipHeight means the newly updated height of + // the tip of compaction chain is invalid, usually means it's smaller than + // current cached one. + ErrInvalidCompactionChainTipHeight = fmt.Errorf( + "invalid compaction chain tip height") + // ErrDKGPrivateKeyExists raised when attempting to save DKG private key + // that already saved. + ErrDKGPrivateKeyExists = errors.New("dkg private key exists") + // ErrDKGPrivateKeyDoesNotExist raised when the DKG private key of the + // requested round does not exists. + ErrDKGPrivateKeyDoesNotExist = errors.New("dkg private key does not exists") ) // Database is the interface for a Database. @@ -55,12 +67,23 @@ type Reader interface { HasBlock(hash common.Hash) bool GetBlock(hash common.Hash) (types.Block, error) GetAllBlocks() (BlockIterator, error) + + // GetCompactionChainTipInfo returns the block hash and finalization height + // of the tip block of compaction chain. Empty hash and zero height means + // the compaction chain is empty. + GetCompactionChainTipInfo() (common.Hash, uint64) + + // DKG Private Key related methods. + HasDKGPrivateKey(round uint64) (bool, error) + GetDKGPrivateKey(round uint64) (dkg.PrivateKey, error) } // Writer defines the interface for writing blocks into DB. type Writer interface { UpdateBlock(block types.Block) error PutBlock(block types.Block) error + PutCompactionChainTipInfo(common.Hash, uint64) error + PutDKGPrivateKey(uint64, dkg.PrivateKey) error } // BlockIterator defines an iterator on blocks hold diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go index 6983d3a5e..3b5994b26 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go @@ -18,14 +18,27 @@ package db import ( - "encoding/json" + "encoding/binary" "github.com/syndtr/goleveldb/leveldb" "github.com/dexon-foundation/dexon-consensus/common" + "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg" "github.com/dexon-foundation/dexon-consensus/core/types" + "github.com/dexon-foundation/dexon/rlp" ) +var ( + blockKeyPrefix = []byte("b-") + compactionChainTipInfoKey = []byte("cc-tip") + dkgPrivateKeyKeyPrefix = []byte("dkg-prvs") +) + +type compactionChainTipInfo struct { + Height uint64 `json:"height"` + Hash common.Hash `json:"hash"` +} + // LevelDBBackedDB is a leveldb backed DB implementation. type LevelDBBackedDB struct { db *leveldb.DB @@ -50,7 +63,7 @@ func (lvl *LevelDBBackedDB) Close() error { // HasBlock implements the Reader.Has method. func (lvl *LevelDBBackedDB) HasBlock(hash common.Hash) bool { - exists, err := lvl.db.Has([]byte(hash[:]), nil) + exists, err := lvl.internalHasBlock(lvl.getBlockKey(hash)) if err != nil { // TODO(missionliao): Modify the interface to return error. panic(err) @@ -58,18 +71,25 @@ func (lvl *LevelDBBackedDB) HasBlock(hash common.Hash) bool { return exists } +func (lvl *LevelDBBackedDB) internalHasBlock(key []byte) (bool, error) { + exists, err := lvl.db.Has(key, nil) + if err != nil { + return false, err + } + return exists, nil +} + // GetBlock implements the Reader.GetBlock method. func (lvl *LevelDBBackedDB) GetBlock( hash common.Hash) (block types.Block, err error) { - - queried, err := lvl.db.Get([]byte(hash[:]), nil) + queried, err := lvl.db.Get(lvl.getBlockKey(hash), nil) if err != nil { if err == leveldb.ErrNotFound { err = ErrBlockDoesNotExist } return } - err = json.Unmarshal(queried, &block) + err = rlp.DecodeBytes(queried, &block) if err != nil { return } @@ -80,20 +100,20 @@ func (lvl *LevelDBBackedDB) GetBlock( func (lvl *LevelDBBackedDB) UpdateBlock(block types.Block) (err error) { // NOTE: we didn't handle changes of block hash (and it // should not happen). - marshaled, err := json.Marshal(&block) + marshaled, err := rlp.EncodeToBytes(&block) if err != nil { return } - - if !lvl.HasBlock(block.Hash) { + blockKey := lvl.getBlockKey(block.Hash) + exists, err := lvl.internalHasBlock(blockKey) + if err != nil { + return + } + if !exists { err = ErrBlockDoesNotExist return } - err = lvl.db.Put( - []byte(block.Hash[:]), - marshaled, - nil) - if err != nil { + if err = lvl.db.Put(blockKey, marshaled, nil); err != nil { return } return @@ -101,19 +121,20 @@ func (lvl *LevelDBBackedDB) UpdateBlock(block types.Block) (err error) { // PutBlock implements the Writer.PutBlock method. func (lvl *LevelDBBackedDB) PutBlock(block types.Block) (err error) { - marshaled, err := json.Marshal(&block) + marshaled, err := rlp.EncodeToBytes(&block) if err != nil { return } - if lvl.HasBlock(block.Hash) { + blockKey := lvl.getBlockKey(block.Hash) + exists, err := lvl.internalHasBlock(blockKey) + if err != nil { + return + } + if exists { err = ErrBlockExists return } - err = lvl.db.Put( - []byte(block.Hash[:]), - marshaled, - nil) - if err != nil { + if err = lvl.db.Put(blockKey, marshaled, nil); err != nil { return } return @@ -125,3 +146,118 @@ func (lvl *LevelDBBackedDB) GetAllBlocks() (BlockIterator, error) { // TODO (mission): Implement this part via goleveldb's iterator. return nil, ErrNotImplemented } + +// PutCompactionChainTipInfo saves tip of compaction chain into the database. +func (lvl *LevelDBBackedDB) PutCompactionChainTipInfo( + blockHash common.Hash, height uint64) error { + marshaled, err := rlp.EncodeToBytes(&compactionChainTipInfo{ + Hash: blockHash, + Height: height, + }) + if err != nil { + return err + } + // Check current cached tip info to make sure the one to be updated is + // valid. + info, err := lvl.internalGetCompactionChainTipInfo() + if err != nil { + return err + } + if info.Height >= height { + return ErrInvalidCompactionChainTipHeight + } + if err = lvl.db.Put(compactionChainTipInfoKey, marshaled, nil); err != nil { + return err + } + return nil +} + +func (lvl *LevelDBBackedDB) internalGetCompactionChainTipInfo() ( + info compactionChainTipInfo, err error) { + queried, err := lvl.db.Get(compactionChainTipInfoKey, nil) + if err != nil { + if err == leveldb.ErrNotFound { + err = nil + } + return + } + if err = rlp.DecodeBytes(queried, &info); err != nil { + return + } + return +} + +// GetCompactionChainTipInfo get the tip info of compaction chain into the +// database. +func (lvl *LevelDBBackedDB) GetCompactionChainTipInfo() ( + hash common.Hash, height uint64) { + info, err := lvl.internalGetCompactionChainTipInfo() + if err != nil { + panic(err) + } + hash, height = info.Hash, info.Height + return +} + +// HasDKGPrivateKey check existence of DKG private key of one round. +func (lvl *LevelDBBackedDB) HasDKGPrivateKey(round uint64) (bool, error) { + exists, err := lvl.db.Has(lvl.getDKGPrivateKeyKey(round), nil) + if err != nil { + return false, err + } + return exists, nil +} + +// GetDKGPrivateKey get DKG private key of one round. +func (lvl *LevelDBBackedDB) GetDKGPrivateKey(round uint64) ( + prv dkg.PrivateKey, err error) { + queried, err := lvl.db.Get(lvl.getDKGPrivateKeyKey(round), nil) + if err != nil { + if err == leveldb.ErrNotFound { + err = ErrDKGPrivateKeyDoesNotExist + } + return + } + if err = rlp.DecodeBytes(queried, &prv); err != nil { + return + } + return +} + +// PutDKGPrivateKey save DKG private key of one round. +func (lvl *LevelDBBackedDB) PutDKGPrivateKey( + round uint64, prv dkg.PrivateKey) error { + // Check existence. + exists, err := lvl.HasDKGPrivateKey(round) + if err != nil { + return err + } + if exists { + return ErrDKGPrivateKeyExists + } + marshaled, err := rlp.EncodeToBytes(&prv) + if err != nil { + return err + } + if err := lvl.db.Put( + lvl.getDKGPrivateKeyKey(round), marshaled, nil); err != nil { + return err + } + return nil +} + +func (lvl *LevelDBBackedDB) getBlockKey(hash common.Hash) (ret []byte) { + ret = make([]byte, len(blockKeyPrefix)+len(hash[:])) + copy(ret, blockKeyPrefix) + copy(ret[len(blockKeyPrefix):], hash[:]) + return +} + +func (lvl *LevelDBBackedDB) getDKGPrivateKeyKey( + round uint64) (ret []byte) { + ret = make([]byte, len(dkgPrivateKeyKeyPrefix)+8) + copy(ret, dkgPrivateKeyKeyPrefix) + binary.LittleEndian.PutUint64( + ret[len(dkgPrivateKeyKeyPrefix):], round) + return +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go index 4246e4fe1..7393de9db 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go @@ -24,6 +24,7 @@ import ( "sync" "github.com/dexon-foundation/dexon-consensus/common" + "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg" "github.com/dexon-foundation/dexon-consensus/core/types" ) @@ -41,10 +42,15 @@ func (seq *blockSeqIterator) NextBlock() (types.Block, error) { // MemBackedDB is a memory backed DB implementation. type MemBackedDB struct { - blocksMutex sync.RWMutex - blockHashSequence common.Hashes - blocksByHash map[common.Hash]*types.Block - persistantFilePath string + blocksLock sync.RWMutex + blockHashSequence common.Hashes + blocksByHash map[common.Hash]*types.Block + compactionChainTipLock sync.RWMutex + compactionChainTipHash common.Hash + compactionChainTipHeight uint64 + dkgPrivateKeysLock sync.RWMutex + dkgPrivateKeys map[uint64]*dkg.PrivateKey + persistantFilePath string } // NewMemBackedDB initialize a memory-backed database. @@ -53,6 +59,7 @@ func NewMemBackedDB(persistantFilePath ...string) ( dbInst = &MemBackedDB{ blockHashSequence: common.Hashes{}, blocksByHash: make(map[common.Hash]*types.Block), + dkgPrivateKeys: make(map[uint64]*dkg.PrivateKey), } if len(persistantFilePath) == 0 || len(persistantFilePath[0]) == 0 { return @@ -87,8 +94,8 @@ func NewMemBackedDB(persistantFilePath ...string) ( // HasBlock returns wheter or not the DB has a block identified with the hash. func (m *MemBackedDB) HasBlock(hash common.Hash) bool { - m.blocksMutex.RLock() - defer m.blocksMutex.RUnlock() + m.blocksLock.RLock() + defer m.blocksLock.RUnlock() _, ok := m.blocksByHash[hash] return ok @@ -96,8 +103,8 @@ func (m *MemBackedDB) HasBlock(hash common.Hash) bool { // GetBlock returns a block given a hash. func (m *MemBackedDB) GetBlock(hash common.Hash) (types.Block, error) { - m.blocksMutex.RLock() - defer m.blocksMutex.RUnlock() + m.blocksLock.RLock() + defer m.blocksLock.RUnlock() return m.internalGetBlock(hash) } @@ -116,8 +123,8 @@ func (m *MemBackedDB) PutBlock(block types.Block) error { return ErrBlockExists } - m.blocksMutex.Lock() - defer m.blocksMutex.Unlock() + m.blocksLock.Lock() + defer m.blocksLock.Unlock() m.blockHashSequence = append(m.blockHashSequence, block.Hash) m.blocksByHash[block.Hash] = &block @@ -130,13 +137,66 @@ func (m *MemBackedDB) UpdateBlock(block types.Block) error { return ErrBlockDoesNotExist } - m.blocksMutex.Lock() - defer m.blocksMutex.Unlock() + m.blocksLock.Lock() + defer m.blocksLock.Unlock() m.blocksByHash[block.Hash] = &block return nil } +// PutCompactionChainTipInfo saves tip of compaction chain into the database. +func (m *MemBackedDB) PutCompactionChainTipInfo( + blockHash common.Hash, height uint64) error { + m.compactionChainTipLock.Lock() + defer m.compactionChainTipLock.Unlock() + if m.compactionChainTipHeight >= height { + return ErrInvalidCompactionChainTipHeight + } + m.compactionChainTipHeight = height + m.compactionChainTipHash = blockHash + return nil +} + +// GetCompactionChainTipInfo get the tip info of compaction chain into the +// database. +func (m *MemBackedDB) GetCompactionChainTipInfo() ( + hash common.Hash, height uint64) { + m.compactionChainTipLock.RLock() + defer m.compactionChainTipLock.RUnlock() + return m.compactionChainTipHash, m.compactionChainTipHeight +} + +// HasDKGPrivateKey check existence of DKG private key of one round. +func (m *MemBackedDB) HasDKGPrivateKey(round uint64) (bool, error) { + m.dkgPrivateKeysLock.RLock() + defer m.dkgPrivateKeysLock.RUnlock() + _, exists := m.dkgPrivateKeys[round] + return exists, nil +} + +// GetDKGPrivateKey get DKG private key of one round. +func (m *MemBackedDB) GetDKGPrivateKey(round uint64) ( + dkg.PrivateKey, error) { + m.dkgPrivateKeysLock.RLock() + defer m.dkgPrivateKeysLock.RUnlock() + if prv, exists := m.dkgPrivateKeys[round]; exists { + return *prv, nil + } + return dkg.PrivateKey{}, ErrDKGPrivateKeyDoesNotExist +} + +// PutDKGPrivateKey save DKG private key of one round. +func (m *MemBackedDB) PutDKGPrivateKey( + round uint64, prv dkg.PrivateKey) error { + m.dkgPrivateKeysLock.Lock() + defer m.dkgPrivateKeysLock.Unlock() + if _, exists := m.dkgPrivateKeys[round]; exists { + return ErrDKGPrivateKeyExists + } + m.dkgPrivateKeys[round] = &prv + return nil +} + // Close implement Closer interface, which would release allocated resource. func (m *MemBackedDB) Close() (err error) { // Save internal state to a pretty-print json file. It's a temporary way @@ -145,8 +205,8 @@ func (m *MemBackedDB) Close() (err error) { return } - m.blocksMutex.RLock() - defer m.blocksMutex.RUnlock() + m.blocksLock.RLock() + defer m.blocksLock.RUnlock() toDump := struct { Sequence common.Hashes @@ -167,8 +227,8 @@ func (m *MemBackedDB) Close() (err error) { } func (m *MemBackedDB) getBlockByIndex(idx int) (types.Block, error) { - m.blocksMutex.RLock() - defer m.blocksMutex.RUnlock() + m.blocksLock.RLock() + defer m.blocksLock.RUnlock() if idx >= len(m.blockHashSequence) { return types.Block{}, ErrIterationFinished diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go index 6645ecbae..8e03cbbda 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go @@ -378,16 +378,21 @@ func NewDKGGroupPublicKey( // Calculate qualify members. disqualifyIDs := map[types.NodeID]struct{}{} - complaintsByID := map[types.NodeID]int{} + complaintsByID := map[types.NodeID]map[types.NodeID]struct{}{} for _, complaint := range complaints { if complaint.IsNack() { - complaintsByID[complaint.PrivateShare.ProposerID]++ + if _, exist := complaintsByID[complaint.PrivateShare.ProposerID]; !exist { + complaintsByID[complaint.PrivateShare.ProposerID] = + make(map[types.NodeID]struct{}) + } + complaintsByID[complaint.PrivateShare.ProposerID][complaint.ProposerID] = + struct{}{} } else { disqualifyIDs[complaint.PrivateShare.ProposerID] = struct{}{} } } - for nID, num := range complaintsByID { - if num > threshold { + for nID, complaints := range complaintsByID { + if len(complaints) > threshold { disqualifyIDs[nID] = struct{}{} } } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go index 2ef243757..671d68018 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go @@ -231,3 +231,7 @@ func isCircleCI() bool { func isTravisCI() bool { return isCI() && os.Getenv("TRAVIS") == "true" } + +func getDKGThreshold(config *types.Config) int { + return int(config.DKGSetSize/3) + 1 +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 81119dc09..add9ce181 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -105,14 +105,14 @@ { "checksumSHA1": "65L1yf+f0OCiLFniljqfRxVdsQA=", "path": "github.com/dexon-foundation/dexon-consensus/common", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { - "checksumSHA1": "7Oa+i0v4Xtpk+mA1ANSwPzOZrZo=", + "checksumSHA1": "jZLh+ZsuMIiMuksK9c/5QpMQ2IM=", "path": "github.com/dexon-foundation/dexon-consensus/core", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { "checksumSHA1": "v4fKR7uhoyufi6hAVO44cFEb+tY=", @@ -123,44 +123,44 @@ { "checksumSHA1": "tQSbYCu5P00lUhKsx3IbBZCuSLY=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { - "checksumSHA1": "p2jOAulavUU2xyj018pYPHlj8XA=", + "checksumSHA1": "W2P7pkuJ+26BpJg03K4Y0nB5obI=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { "checksumSHA1": "6Pf6caC8LTNCI7IflFmglKYnxYo=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { - "checksumSHA1": "oE4WEMp92OrJrujzd3XdQEkPiDI=", + "checksumSHA1": "trkFVPLd7UFFUzL8bn6KuvFU9gE=", "path": "github.com/dexon-foundation/dexon-consensus/core/db", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { "checksumSHA1": "Z079qQV+aQV9A3kSJ0LbFjx5VO4=", "path": "github.com/dexon-foundation/dexon-consensus/core/types", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { "checksumSHA1": "Sn3PAYsblIXmr7gVKDzxnoBPku4=", "path": "github.com/dexon-foundation/dexon-consensus/core/types/dkg", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { "checksumSHA1": "A7UQ+7rv9FuElmFBEn/ZdhBqFKI=", "path": "github.com/dexon-foundation/dexon-consensus/core/utils", - "revision": "0ead4a7c012af9ddaa4a934729e216539d2caeb1", - "revisionTime": "2018-12-13T03:01:43Z" + "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46", + "revisionTime": "2018-12-16T06:44:15Z" }, { "checksumSHA1": "TAkwduKZqLyimyTPPWIllZWYFuE=", |