diff --git a/container/ethereum.go b/container/ethereum.go index 92a0fcc2..594fef1a 100644 --- a/container/ethereum.go +++ b/container/ethereum.go @@ -70,6 +70,7 @@ type Ethereum interface { NewIstanbulClient() *istclient.Client ConsensusMonitor(err chan<- error, quit chan struct{}) + WaitForProposed(expectedAddress common.Address, t time.Duration) error WaitForPeersConnected(int) error WaitForBlocks(int) error WaitForBlockHeight(int) error @@ -401,7 +402,7 @@ func (eth *ethereum) ConsensusMonitor(errCh chan<- error, quit chan struct{}) { log.Printf("Connection lost: %v", err) errCh <- err return - case <-timer.C: + case <-timer.C: // FIXME: this event may be missed if blockNumber == 0 { errCh <- ErrNoBlock } else { @@ -423,6 +424,33 @@ func (eth *ethereum) ConsensusMonitor(errCh chan<- error, quit chan struct{}) { } } +// TODO: refactor with ConsensusMonitor +func (eth *ethereum) WaitForProposed(expectedAddress common.Address, timeout time.Duration) error { + cli := eth.NewClient() + + subCh := make(chan *ethtypes.Header) + + sub, err := cli.SubscribeNewHead(context.Background(), subCh) + if err != nil { + return err + } + defer sub.Unsubscribe() + + timer := time.NewTimer(timeout) + for { + select { + case err := <-sub.Err(): + return err + case <-timer.C: // FIXME: this event may be missed + return errors.New("no result") + case head := <-subCh: + if getProposer(head) == expectedAddress { + return nil + } + } + } +} + func (eth *ethereum) WaitForPeersConnected(expectedPeercount int) error { client := eth.NewIstanbulClient() if client == nil { diff --git a/container/utils.go b/container/utils.go index e8481827..86a33e5b 100644 --- a/container/utils.go +++ b/container/utils.go @@ -24,7 +24,12 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" + "github.com/getamis/istanbul-tools/cmd/istanbul/extradata" uuid "github.com/satori/go.uuid" ) @@ -78,3 +83,29 @@ func saveNodeKey(key *ecdsa.PrivateKey, dataDir string) error { } return nil } + +func sigHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewKeccak256() + + // Clean seal is required for calculating proposer seal. + rlp.Encode(hasher, types.IstanbulFilteredHeader(header, false)) + hasher.Sum(hash[:0]) + return hash +} + +func getProposer(header *types.Header) common.Address { + if header == nil { + return common.Address{} + } + + _, istanbulExtra, err := extradata.Decode(common.ToHex(header.Extra)) + if err != nil { + return common.Address{} + } + + addr, err := istanbul.GetSignatureAddress(sigHash(header).Bytes(), istanbulExtra.Seal) + if err != nil { + return common.Address{} + } + return addr +} diff --git a/tests/dynamic_test.go b/tests/dynamic_test.go index 30ea1f49..a7bb6fb4 100644 --- a/tests/dynamic_test.go +++ b/tests/dynamic_test.go @@ -80,6 +80,18 @@ var _ = Describe("Dynamic validators addition/removal testing", func() { }) }) + It("TFS-02-02 New validators consensus participation", func() { + testValidator := 1 + + newValidators, err := blockchain.AddValidators(testValidator) + Expect(err).Should(BeNil()) + + waitFor(blockchain.Validators()[numberOfValidators:], func(eth container.Ethereum, wg *sync.WaitGroup) { + Expect(eth.WaitForProposed(newValidators[0].Address(), 100*time.Second)).Should(BeNil()) + wg.Done() + }) + }) + It("TFS-02-03 Remove validators", func() { numOfCandidates := 3