diff options
author | Jimmy Hu <jimmy.hu@dexon.org> | 2019-01-23 13:04:02 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-03-12 12:19:09 +0800 |
commit | d2eda44dcaa123b34a151831c4568fae8f2eef85 (patch) | |
tree | 2cc706c5ae9e563c2362e8c0c724f7b163ccfd03 | |
parent | 1fd3c43990a9185db709a025ea5110f2930a3587 (diff) | |
download | dexon-d2eda44dcaa123b34a151831c4568fae8f2eef85.tar.gz dexon-d2eda44dcaa123b34a151831c4568fae8f2eef85.tar.zst dexon-d2eda44dcaa123b34a151831c4568fae8f2eef85.zip |
params: Update testnet config (#167)
* vendor: sync to latest core
* param: update testnet config
* params: update dmoment
13 files changed, 122 insertions, 79 deletions
diff --git a/params/config.go b/params/config.go index 90b1d2e31..b9b99e675 100644 --- a/params/config.go +++ b/params/config.go @@ -27,7 +27,7 @@ import ( // Genesis hashes to enforce below configs on. var ( MainnetGenesisHash = common.HexToHash("0xe972af2797b02f4dab95ffa229714c35d5c55685f20261b9498c8b8a3fe33856") - TestnetGenesisHash = common.HexToHash("0xaa4414cf836c517a6c4e034d55f5133476d5795bfd2247a7f3375bce2bc3cc56") + TestnetGenesisHash = common.HexToHash("0xd38d11902c6e134bb85112c05a8faa92ae5189474591ac1343e849be29d7d643") ) var ( @@ -83,7 +83,7 @@ var ( // TestnetChainConfig contains the chain parameters to run a node on the Taiwan test network. TestnetChainConfig = &ChainConfig{ ChainID: big.NewInt(238), - DMoment: 1547962500, + DMoment: 1548124200, HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, @@ -104,12 +104,12 @@ var ( BlockGasLimit: 40000000, NumChains: 6, LambdaBA: 250, - LambdaDKG: 2500, + LambdaDKG: 4000, K: 0, PhiRatio: 0.667, NotarySetSize: 4, DKGSetSize: 4, - RoundInterval: 600000, + RoundInterval: 1200000, MinBlockInterval: 1000, FineValues: []*big.Int{ new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e4)), diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/common/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/common/utils.go index 7e89c059d..63d25a3fc 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/common/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/common/utils.go @@ -2,13 +2,20 @@ package common import ( "math/rand" + "time" ) +var random *rand.Rand + +func init() { + random = rand.New(rand.NewSource(time.Now().Unix())) +} + // NewRandomHash returns a random Hash-like value. func NewRandomHash() Hash { x := Hash{} for i := 0; i < HashLength; i++ { - x[i] = byte(rand.Int() % 256) + x[i] = byte(random.Int() % 256) } return x } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go index d3cf533c6..9e863696a 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go @@ -258,7 +258,7 @@ func (mgr *agreementMgr) processAgreementResult( if isStop(aID) { return nil } - if result.Position == aID { + if result.Position == aID && !agreement.confirmed() { mgr.logger.Info("Syncing BA", "position", &result.Position) for key := range result.Votes { if err := agreement.processVote(&result.Votes[key]); err != nil { diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-state.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-state.go index 266e44294..5b2ce52e7 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-state.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-state.go @@ -37,7 +37,6 @@ type agreementStateType int const ( stateFast agreementStateType = iota stateFastVote - stateFastRollback stateInitial statePreCommit stateCommit @@ -98,23 +97,8 @@ func newFastVoteState(a *agreementData) *fastVoteState { } func (s *fastVoteState) state() agreementStateType { return stateFastVote } -func (s *fastVoteState) clocks() int { return 2 } +func (s *fastVoteState) clocks() int { return 3 } func (s *fastVoteState) nextState() (agreementState, error) { - return newFastRollbackState(s.a), nil -} - -//----- FastRollbackState ----- -type fastRollbackState struct { - a *agreementData -} - -func newFastRollbackState(a *agreementData) *fastRollbackState { - return &fastRollbackState{a: a} -} - -func (s *fastRollbackState) state() agreementStateType { return stateFastRollback } -func (s *fastRollbackState) clocks() int { return 1 } -func (s *fastRollbackState) nextState() (agreementState, error) { return newInitialState(s.a), nil } @@ -182,8 +166,10 @@ func (s *commitState) nextState() (agreementState, error) { defer s.a.lock.Unlock() hash, ok := s.a.countVoteNoLock(s.a.period, types.VotePreCom) if ok && hash != skipBlockHash { - s.a.lockValue = hash - s.a.lockRound = s.a.period + if s.a.period > s.a.lockIter { + s.a.lockValue = hash + s.a.lockIter = s.a.period + } } else { hash = skipBlockHash } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go index c17c59f8a..97848c5e4 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go @@ -96,7 +96,7 @@ type agreementData struct { isLeader bool leader *leaderSelector lockValue common.Hash - lockRound uint64 + lockIter uint64 period uint64 requiredVote int votes map[uint64][]map[types.NodeID]*types.Vote @@ -111,6 +111,7 @@ type agreement struct { data *agreementData aID *atomic.Value notarySet map[types.NodeID]struct{} + hasVoteFast bool hasOutput bool lock sync.RWMutex pendingBlock []pendingBlock @@ -168,9 +169,10 @@ func (a *agreement) restart( a.data.requiredVote = len(notarySet)/3*2 + 1 a.data.leader.restart(crs) a.data.lockValue = nullBlockHash - a.data.lockRound = 0 + a.data.lockIter = 0 a.data.isLeader = a.data.ID == leader a.fastForward = make(chan uint64, 1) + a.hasVoteFast = false a.hasOutput = false a.state = newFastState(a.data) a.notarySet = notarySet @@ -265,7 +267,7 @@ func (a *agreement) pullVotes() bool { a.data.lock.RLock() defer a.data.lock.RUnlock() return a.state.state() == statePullVote || - a.state.state() == stateFastRollback || + a.state.state() == stateInitial || (a.state.state() == statePreCommit && (a.data.period%3) == 0) } @@ -382,12 +384,24 @@ func (a *agreement) processVote(vote *types.Vote) error { } a.data.votes[vote.Period][vote.Type][vote.ProposerID] = vote if !a.hasOutput && - (vote.Type == types.VoteCom || vote.Type == types.VoteFast) { + (vote.Type == types.VoteCom || + vote.Type == types.VoteFast || + vote.Type == types.VoteFastCom) { if hash, ok := a.data.countVoteNoLock(vote.Period, vote.Type); ok && hash != skipBlockHash { - a.hasOutput = true - a.data.recv.ConfirmBlock(hash, - a.data.votes[vote.Period][vote.Type]) + if vote.Type == types.VoteFast { + if !a.hasVoteFast { + a.data.recv.ProposeVote( + types.NewVote(types.VoteFastCom, hash, vote.Period)) + a.data.lockValue = hash + a.data.lockIter = math.MaxUint64 + a.hasVoteFast = true + } + } else { + a.hasOutput = true + a.data.recv.ConfirmBlock(hash, + a.data.votes[vote.Period][vote.Type]) + } return nil } } else if a.hasOutput { @@ -402,16 +416,18 @@ func (a *agreement) processVote(vote *types.Vote) error { if hash, ok := a.data.countVoteNoLock(vote.Period, vote.Type); ok && hash != skipBlockHash { // Condition 1. - if a.data.period >= vote.Period && vote.Period > a.data.lockRound && + if a.data.period >= vote.Period && vote.Period > a.data.lockIter && vote.BlockHash != a.data.lockValue { a.data.lockValue = hash - a.data.lockRound = vote.Period + a.data.lockIter = vote.Period return nil } // Condition 2. if vote.Period > a.data.period { - a.data.lockValue = hash - a.data.lockRound = vote.Period + if vote.Period > a.data.lockIter { + a.data.lockValue = hash + a.data.lockIter = vote.Period + } a.fastForward <- vote.Period return nil } @@ -466,6 +482,12 @@ func (a *agreement) done() <-chan struct{} { return ch } +func (a *agreement) confirmed() bool { + a.lock.RLock() + defer a.lock.RUnlock() + return a.hasOutput +} + // processBlock is the entry point for processing Block. func (a *agreement) processBlock(block *types.Block) error { a.lock.Lock() @@ -499,7 +521,8 @@ func (a *agreement) processBlock(block *types.Block) error { } a.data.blocks[block.ProposerID] = block a.addCandidateBlockNoLock(block) - if (a.state.state() == stateFast || a.state.state() == stateFastVote) && + if block.ProposerID != a.data.ID && + (a.state.state() == stateFast || a.state.state() == stateFastVote) && block.ProposerID == a.leader() { go func() { for func() bool { 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 fbd691f73..bec47f491 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 @@ -163,7 +163,10 @@ func (cc *configurationChain) runDKG(round uint64) error { return nil } // Phase 2(T = 0): Exchange DKG secret key share. - cc.dkg.processMasterPublicKeys(mpks) + if err := cc.dkg.processMasterPublicKeys(mpks); err != nil { + cc.logger.Error("Failed to process master public key", + "error", err) + } cc.mpkReady = true for _, prvShare := range cc.pendingPrvShare { if err := cc.dkg.processPrivateShare(prvShare); err != nil { @@ -184,7 +187,10 @@ func (cc *configurationChain) runDKG(round uint64) error { // Phase 5(T = 2λ): Propose Anti nack complaint. cc.logger.Debug("Calling Governance.DKGComplaints", "round", round) complaints := cc.gov.DKGComplaints(round) - cc.dkg.processNackComplaints(complaints) + if err := cc.dkg.processNackComplaints(complaints); err != nil { + cc.logger.Error("Failed to process NackComplaint", + "error", err) + } cc.dkgLock.Unlock() <-ticker.Tick() cc.dkgLock.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 b84947a86..2cef6bc6d 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go @@ -49,14 +49,6 @@ var ( "incorrect agreement result position") ErrNotEnoughVotes = fmt.Errorf( "not enought votes") - ErrIncorrectVoteBlockHash = fmt.Errorf( - "incorrect vote block hash") - ErrIncorrectVoteType = fmt.Errorf( - "incorrect vote type") - ErrIncorrectVotePosition = fmt.Errorf( - "incorrect vote position") - ErrIncorrectVoteProposer = fmt.Errorf( - "incorrect vote proposer") ErrCRSNotReady = fmt.Errorf( "CRS not ready") ErrConfigurationNotReady = fmt.Errorf( diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go index 5be16847d..e43ebc806 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go @@ -45,7 +45,9 @@ const cryptoType = "bls" var publicKeyLength int func init() { - bls.Init(curve) + if err := bls.Init(curve); err != nil { + panic(err) + } pubKey := &bls.PublicKey{} publicKeyLength = len(pubKey.Serialize()) @@ -276,6 +278,7 @@ func (pubs *PublicKeyShares) Clone() *PublicKeyShares { // NewID creates a ew ID structure. func NewID(id []byte) ID { var blsID bls.ID + // #nosec G104 blsID.SetLittleEndian(id) return blsID } @@ -284,6 +287,7 @@ func NewID(id []byte) ID { // It returns err if the byte slice is not valid. func BytesID(id []byte) (ID, error) { var blsID bls.ID + // #nosec G104 err := blsID.SetLittleEndian(id) return blsID, err } @@ -325,6 +329,7 @@ func (prvs *PrivateKeyShares) SetParticipants(IDs IDs) { prvs.shares = make([]PrivateKey, len(IDs)) prvs.shareIndex = make(map[ID]int, len(IDs)) for idx, ID := range IDs { + // #nosec G104 prvs.shares[idx].privateKey.Set(prvs.masterPrivateKey, &ID) prvs.shareIndex[ID] = idx } @@ -411,7 +416,9 @@ func (pubs *PublicKeyShares) Share(ID ID) (*PublicKey, error) { if err := pk.publicKey.Set(pubs.masterPublicKey, &ID); err != nil { return nil, err } - pubs.AddShare(ID, &pk) + if err := pubs.AddShare(ID, &pk); err != nil { + return nil, err + } return &pk, nil } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa/ecdsa.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa/ecdsa.go index 82e4dca4b..9da5f4fb5 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa/ecdsa.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa/ecdsa.go @@ -29,7 +29,9 @@ import ( const cryptoType = "ecdsa" func init() { - crypto.RegisterSigToPub(cryptoType, SigToPub) + if err := crypto.RegisterSigToPub(cryptoType, SigToPub); err != nil { + panic(err) + } } // PrivateKey represents a private key structure used in geth and implments 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 7076ef376..02e3ae070 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 @@ -172,7 +172,7 @@ func newDKGProtocol( } func (d *dkgProtocol) processMasterPublicKeys( - mpks []*typesDKG.MasterPublicKey) error { + mpks []*typesDKG.MasterPublicKey) (err error) { d.idMap = make(map[types.NodeID]dkg.ID, len(mpks)) d.mpkMap = make(map[types.NodeID]*dkg.PublicKeyShares, len(mpks)) d.prvSharesReceived = make(map[types.NodeID]struct{}, len(mpks)) @@ -187,7 +187,8 @@ func (d *dkgProtocol) processMasterPublicKeys( for _, mpk := range mpks { share, ok := d.masterPrivateShare.Share(mpk.DKGID) if !ok { - return ErrIDShareNotFound + err = ErrIDShareNotFound + continue } d.recv.ProposeDKGPrivateShare(&typesDKG.PrivateShare{ ReceiverID: mpk.ProposerID, @@ -195,7 +196,7 @@ func (d *dkgProtocol) processMasterPublicKeys( PrivateShare: *share, }) } - return nil + return } func (d *dkgProtocol) proposeNackComplaints() { diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go index 97044f5aa..ae86e51cc 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go @@ -33,6 +33,7 @@ const ( VotePreCom VoteCom VoteFast + VoteFastCom // Do not add any type below MaxVoteType. MaxVoteType ) 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 3b1069eb8..14780e73b 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go @@ -32,6 +32,20 @@ import ( "github.com/dexon-foundation/dexon-consensus/core/utils" ) +// Errors for utils. +var ( + ErrIncorrectVoteBlockHash = fmt.Errorf( + "incorrect vote block hash") + ErrIncorrectVoteType = fmt.Errorf( + "incorrect vote type") + ErrIncorrectVotePosition = fmt.Errorf( + "incorrect vote position") + ErrIncorrectVoteProposer = fmt.Errorf( + "incorrect vote proposer") + ErrIncorrectVotePeriod = fmt.Errorf( + "incorrect vote period") +) + // NodeSetCache is type alias to avoid fullnode compile error when moving // it to core/utils package. type NodeSetCache = utils.NodeSetCache @@ -161,10 +175,14 @@ func VerifyAgreementResult( } voted := make(map[types.NodeID]struct{}, len(notarySet)) voteType := res.Votes[0].Type - if voteType != types.VoteFast && voteType != types.VoteCom { + votePeriod := res.Votes[0].Period + if voteType != types.VoteFastCom && voteType != types.VoteCom { return ErrIncorrectVoteType } for _, vote := range res.Votes { + if vote.Period != votePeriod { + return ErrIncorrectVotePeriod + } if res.IsEmptyBlock { if (vote.BlockHash != common.Hash{}) { return ErrIncorrectVoteBlockHash diff --git a/vendor/vendor.json b/vendor/vendor.json index d0f201e20..52ef16cdf 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -139,18 +139,18 @@ "versionExact": "dev" }, { - "checksumSHA1": "ZUuiRqS6PnoNIvBmLStVQiyhkOM=", + "checksumSHA1": "MA1hygDGoOGggSd39fadmgoK0u0=", "path": "github.com/dexon-foundation/dexon-consensus/common", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { - "checksumSHA1": "ki1Mf8qUb5j5ajqWNLggSpa7rac=", + "checksumSHA1": "OAyBHXpwSexhUHmXQ3QV62/3e5I=", "path": "github.com/dexon-foundation/dexon-consensus/core", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, @@ -165,64 +165,64 @@ { "checksumSHA1": "tQSbYCu5P00lUhKsx3IbBZCuSLY=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { - "checksumSHA1": "W2P7pkuJ+26BpJg03K4Y0nB5obI=", + "checksumSHA1": "Nlv7pi1DIBftY+r6CFP8LBIQA3U=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { - "checksumSHA1": "6Pf6caC8LTNCI7IflFmglKYnxYo=", + "checksumSHA1": "BhLKK8RveoLaeXc9UyUKMwQqchU=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { "checksumSHA1": "zpuCdMT8MGsy4pLgHKpg/Wd4izU=", "path": "github.com/dexon-foundation/dexon-consensus/core/db", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { "checksumSHA1": "eq19vhMpc90UUJ7I91ti5P2CkQ0=", "path": "github.com/dexon-foundation/dexon-consensus/core/syncer", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { - "checksumSHA1": "zkrt3MOtqHPB3BmZtppZ9uesw3s=", + "checksumSHA1": "nN9mriru/5WgFfkeKrLCN533evU=", "path": "github.com/dexon-foundation/dexon-consensus/core/types", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { "checksumSHA1": "rmv8uxwrqMhJAeA3RPvwYP8mFro=", "path": "github.com/dexon-foundation/dexon-consensus/core/types/dkg", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, { "checksumSHA1": "FUHa68Hif8F8YHmx4h0sQIUNp40=", "path": "github.com/dexon-foundation/dexon-consensus/core/utils", - "revision": "c5b303f4d143631fb565d4ec8ff3bcc609a4ffd3", - "revisionTime": "2019-01-17T03:00:42Z", + "revision": "caa9ad362b4d57bba8551be4074c86f820b7881c", + "revisionTime": "2019-01-21T05:11:04Z", "version": "master", "versionExact": "master" }, |