func CommitTx(addr string, txBytes []byte) error { wsc := rpcclient.NewWSClient("ws://" + addr + "/websocket") if _, err := wsc.Start(); err != nil { return err } if err := wsc.Subscribe(types.EventStringNewBlock()); err != nil { return err } defer func() { wsc.Unsubscribe(types.EventStringNewBlock()) wsc.Stop() }() c := rpcclient.NewClientURI("http://" + addr) params := map[string]interface{}{ "tx": hex.EncodeToString(txBytes), } var result ctypes.TMResult _, err := c.Call("broadcast_tx", params, &result) if err != nil { return err } timeout := time.After(time.Second * 5) for { select { case data := <-wsc.ResultsCh: if data == nil { return fmt.Errorf("Websocket channel closed") } _, edata, err := ctypes.UnmarshalEvent(data) if err != nil { return fmt.Errorf("Error unmarshalling websocket result: %v", err) } if edata == nil { continue } block := edata.(types.EventDataNewBlock).Block for _, tx := range block.Data.Txs { if bytes.Equal(tx, txBytes) { return nil } } case <-timeout: return fmt.Errorf("Timed out waiting for tx to commit (%x)", txBytes) } } return nil }
// Save Block, save the +2/3 Commits we've seen func (cs *ConsensusState) saveBlock(block *types.Block, blockParts *types.PartSet, commits *types.VoteSet) { // The proposal must be valid. if err := cs.stageBlock(block, blockParts); err != nil { PanicSanity(Fmt("saveBlock() an invalid block: %v", err)) } // Save to blockStore. if cs.blockStore.Height() < block.Height { seenValidation := commits.MakeValidation() cs.blockStore.SaveBlock(block, blockParts, seenValidation) } // Commit to proxyAppCtx err := cs.stagedState.Commit(cs.proxyAppCtx) if err != nil { // TODO: handle this gracefully. PanicQ(Fmt("Commit failed for applicaiton")) } // Save the state. cs.stagedState.Save() // Update mempool. cs.mempool.Update(block) // Fire off event if cs.evsw != nil && cs.evc != nil { cs.evsw.FireEvent(types.EventStringNewBlock(), types.EventDataNewBlock{block}) go cs.evc.Flush() } }
func TestTxConcurrentWithCommit(t *testing.T) { state, privVals := randGenesisState(1, false, 10) cs := newConsensusState(state, privVals[0], NewCounterApplication()) height, round := cs.Height, cs.Round newBlockCh := subscribeToEvent(cs.evsw, "tester", types.EventStringNewBlock(), 1) appendTxsRange := func(start, end int) { // Append some txs. for i := start; i < end; i++ { txBytes := make([]byte, 8) binary.BigEndian.PutUint64(txBytes, uint64(i)) err := cs.mempool.CheckTx(txBytes, nil) if err != nil { t.Fatal("Error after CheckTx: %v", err) } // time.Sleep(time.Microsecond * time.Duration(rand.Int63n(3000))) } } NTxs := 10000 go appendTxsRange(0, NTxs) startTestRound(cs, height, round) ticker := time.NewTicker(time.Second * 5) for nTxs := 0; nTxs < NTxs; { select { case b := <-newBlockCh: nTxs += b.(types.EventDataNewBlock).Block.Header.NumTxs case <-ticker.C: t.Fatal("Timed out waiting to commit blocks with transactions") } } }
func (c *Crawler) OnStart() error { // connect to local node first, set info, // and fire peers onto the checkQueue if err := c.pollNode(c.self); err != nil { return err } // connect to weboscket, subscribe to local events // and run the read loop to listen for new blocks _, err := c.self.client.ws.Start() if err != nil { return err } if err := c.self.client.ws.Subscribe(types.EventStringNewBlock()); err != nil { return err } go c.readLoop(c.self) // add ourselves to the nodes list c.nodes[c.self.Address()] = c.self // nodes we hear about get put on the checkQueue // by pollNode and are handled in the checkLoop. // if its a node we're not already connected to, // it gets put on the nodeQueue and // we attempt to connect in the connectLoop go c.checkLoop() go c.connectLoop() return nil }
// Save Block, save the +2/3 Commits we've seen func (cs *ConsensusState) saveBlock(block *types.Block, blockParts *types.PartSet, commits *types.VoteSet) { // The proposal must be valid. if err := cs.stageBlock(block, blockParts); err != nil { PanicSanity(Fmt("saveBlock() an invalid block: %v", err)) } // Save to blockStore. if cs.blockStore.Height() < block.Height { seenValidation := commits.MakeValidation() cs.blockStore.SaveBlock(block, blockParts, seenValidation) } // Save the state. cs.stagedState.Save() // Update mempool. cs.mempoolReactor.ResetForBlockAndState(block, cs.stagedState) // Fire off event if cs.evsw != nil && cs.evc != nil { cs.evsw.FireEvent(types.EventStringNewBlock(), types.EventDataNewBlock{block}) go cs.evc.Flush() } }
// run through propose, prevote, precommit commit with two validators // where the first validator has to wait for votes from the second func TestFullRound2(t *testing.T) { cs1, vss := randConsensusState(2) cs2 := vss[1] height, round := cs1.Height, cs1.Round voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1) newBlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewBlock(), 1) // start round and wait for propose and prevote startTestRound(cs1, height, round) <-voteCh // prevote // we should be stuck in limbo waiting for more prevotes propBlockHash, propPartsHeader := cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header() // prevote arrives from cs2: signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlockHash, propPartsHeader) <-voteCh <-voteCh //precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlockHash, propBlockHash) // we should be stuck in limbo waiting for more precommits // precommit arrives from cs2: signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlockHash, propPartsHeader) <-voteCh // wait to finish commit, propose in next height <-newBlockCh }
// receive a few new block messages in a row, with increasing height func TestWSBlockchainGrowth(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } wsc := newWSClient(t) eid := types.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) wsc.Stop() }() // listen for NewBlock, ensure height increases by 1 var initBlockN int for i := 0; i < 3; i++ { waitForEvent(t, wsc, eid, true, func() {}, func(eid string, eventData interface{}) error { block := eventData.(types.EventDataNewBlock).Block if i == 0 { initBlockN = block.Header.Height } else { if block.Header.Height != initBlockN+i { return fmt.Errorf("Expected block %d, got block %d", initBlockN+i, block.Header.Height) } } return nil }) } }
func testGetStorage(t *testing.T, typ string) { con := newWSCon(t) eid := types.EventStringNewBlock() subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) con.Close() }() amt, gasLim, fee := int64(1100), int64(1000), int64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) receipt := broadcastTx(t, typ, tx) if receipt.CreatesContract == 0 { t.Fatal("This tx creates a contract") } if len(receipt.TxHash) == 0 { t.Fatal("Failed to compute tx hash") } contractAddr := receipt.ContractAddr if len(contractAddr) == 0 { t.Fatal("Creates contract but resulting address is empty") } // allow it to get mined waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 v := getStorage(t, typ, contractAddr, []byte{0x1}) got := LeftPadWord256(v) expected := LeftPadWord256([]byte{0x5}) if got.Compare(expected) != 0 { t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes()) } }
// receive a few new block messages in a row, with increasing height func TestWSBlockchainGrowth(t *testing.T) { con := newWSCon(t) eid := types.EventStringNewBlock() subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) con.Close() }() // listen for NewBlock, ensure height increases by 1 unmarshalValidateBlockchain(t, con, eid) }
// receive a new block message func TestWSNewBlock(t *testing.T) { wsc := newWSClient(t) eid := types.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) wsc.Stop() }() waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b interface{}) error { fmt.Println("Check:", b) return nil }) }
// receive a new block message func TestWSNewBlock(t *testing.T) { con := newWSCon(t) eid := types.EventStringNewBlock() subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) con.Close() }() waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error { fmt.Println("Check:", string(b)) return nil }) }
func TestReplayCatchup(t *testing.T) { // write the needed wal to file f, err := ioutil.TempFile(os.TempDir(), "replay_test_") if err != nil { t.Fatal(err) } name := f.Name() _, err = f.WriteString(testLog) if err != nil { t.Fatal(err) } f.Close() cs := fixedConsensusState() // we've already precommitted on the first block // without replay catchup we would be halted here forever cs.privValidator.LastHeight = 1 // first block cs.privValidator.LastStep = 3 // precommit newBlockCh := subscribeToEvent(cs.evsw, "tester", types.EventStringNewBlock(), 0) // start timeout and receive routines cs.startRoutines(0) // open wal and run catchup messages openWAL(t, cs, name) if err := cs.catchupReplay(cs.Height); err != nil { t.Fatalf("Error on catchup replay %v", err) } cs.enterNewRound(cs.Height, cs.Round) after := time.After(time.Second * 2) select { case <-newBlockCh: case <-after: t.Fatal("Timed out waiting for new block") } }
func testCall(t *testing.T, typ string) { con := newWSCon(t) eid := types.EventStringNewBlock() subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) con.Close() }() client := clients[typ] // create the contract amt := uint64(6969) code, _, _ := simpleContract() _, receipt := broadcastTx(t, typ, userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) if receipt.CreatesContract == 0 { t.Fatal("This tx creates a contract") } if len(receipt.TxHash) == 0 { t.Fatal("Failed to compute tx hash") } contractAddr := receipt.ContractAddr if len(contractAddr) == 0 { t.Fatal("Creates contract but resulting address is empty") } // allow it to get mined waitForEvent(t, con, eid, true, func() { }, func(eid string, b []byte) error { return nil }) mempoolCount = 0 // run a call through the contract data := []byte{} expected := []byte{0xb} callContract(t, client, contractAddr, data, expected) }
func testCall(t *testing.T, typ string) { con := newWSCon(t) eid := types.EventStringNewBlock() subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) con.Close() }() client := clients[typ] // create the contract amt, gasLim, fee := int64(6969), int64(1000), int64(1000) code, _, _ := simpleContract() tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) receipt := broadcastTx(t, typ, tx) if receipt.CreatesContract == 0 { t.Fatal("This tx creates a contract") } if len(receipt.TxHash) == 0 { t.Fatal("Failed to compute tx hash") } contractAddr := receipt.ContractAddr if len(contractAddr) == 0 { t.Fatal("Creates contract but resulting address is empty") } // allow it to get mined waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 // run a call through the contract data := []byte{} expected := []byte{0xb} callContract(t, client, user[0].PubKey.Address(), contractAddr, data, expected) }
// 4 vals. // we receive a final precommit after going into next round, but others might have gone to commit already! func TestHalt1(t *testing.T) { cs1, vss := randConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1) newBlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewBlock(), 1) voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, 0) <-newRoundCh re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*RoundState) propBlock := rs.ProposalBlock propBlockParts := propBlock.MakePartSet() <-voteCh // prevote signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlock.Hash(), propBlockParts.Header(), cs3, cs4) <-voteCh // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlock.Hash(), propBlock.Hash()) // add precommits from the rest signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, nil, types.PartSetHeader{}) // didnt receive proposal signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, propBlock.Hash(), propBlockParts.Header()) // we receive this later, but cs3 might receive it earlier and with ours will go to commit! precommit4 := signVote(cs4, types.VoteTypePrecommit, propBlock.Hash(), propBlockParts.Header()) incrementRound(cs2, cs3, cs4) // timeout to new round <-timeoutWaitCh re = <-newRoundCh rs = re.(types.EventDataRoundState).RoundState.(*RoundState) log.Notice("### ONTO ROUND 1") /*Round2 // we timeout and prevote our lock // a polka happened but we didn't see it! */ // go to prevote, prevote for locked block <-voteCh // prevote validatePrevote(t, cs1, 0, vss[0], rs.LockedBlock.Hash()) // now we receive the precommit from the previous round addVoteToFrom(cs1, cs4, precommit4) // receiving that precommit should take us straight to commit <-newBlockCh re = <-newRoundCh rs = re.(types.EventDataRoundState).RoundState.(*RoundState) if rs.Height != 2 { t.Fatal("expected height to increment") } }
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestLockPOLRelock(t *testing.T) { cs1, vss := randConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1) newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1) newBlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewBlock(), 1) log.Debug("cs2 last round", "lr", cs2.PrivValidator.LastRound) // everything done from perspective of cs1 /* Round1 (cs1, B) // B B B B// B nil B nil eg. cs2 and cs4 didn't see the 2/3 prevotes */ // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, 0) <-newRoundCh re := <-proposalCh rs := re.(types.EventDataRoundState).RoundState.(*RoundState) theBlockHash := rs.ProposalBlock.Hash() <-voteCh // prevote signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) _, _, _ = <-voteCh, <-voteCh, <-voteCh // prevotes <-voteCh // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) _, _, _ = <-voteCh, <-voteCh, <-voteCh // precommits // before we timeout to the new round set the new proposal prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) propBlockParts := propBlock.MakePartSet() propBlockHash := propBlock.Hash() incrementRound(cs2, cs3, cs4) // timeout to new round <-timeoutWaitCh //XXX: this isnt gauranteed to get there before the timeoutPropose ... cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer") <-newRoundCh log.Notice("### ONTO ROUND 1") /* Round2 (cs2, C) // B C C C // C C C _) cs1 changes lock! */ // now we're on a new round and not the proposer // but we should receive the proposal select { case <-proposalCh: case <-timeoutProposeCh: <-proposalCh } // go to prevote, prevote for locked block (not proposal), move on <-voteCh validatePrevote(t, cs1, 0, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) _, _, _ = <-voteCh, <-voteCh, <-voteCh // prevotes // now either we go to PrevoteWait or Precommit select { case <-timeoutWaitCh: // we're in PrevoteWait, go to Precommit <-voteCh case <-voteCh: // we went straight to Precommit } // we should have unlocked and locked on the new block validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) signAddVoteToFromMany(types.VoteTypePrecommit, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3) _, _ = <-voteCh, <-voteCh be := <-newBlockCh b := be.(types.EventDataNewBlock) re = <-newRoundCh rs = re.(types.EventDataRoundState).RoundState.(*RoundState) if rs.Height != 2 { t.Fatal("Expected height to increment") } if !bytes.Equal(b.Block.Hash(), propBlockHash) { t.Fatal("Expected new block to be proposal block") } }
func testNameReg(t *testing.T, typ string) { client := clients[typ] con := newWSCon(t) types.MinNameRegistrationPeriod = 1 // register a new name, check if its there // since entries ought to be unique and these run against different clients, we append the typ name := "ye_old_domain_name_" + typ data := "if not now, when" fee := int64(1000) numDesiredBlocks := int64(2) amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) eid := types.EventStringNameReg(name) subscribe(t, con, eid) tx := makeDefaultNameTx(t, typ, name, data, amt, fee) broadcastTx(t, typ, tx) // verify the name by both using the event and by checking get_name waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error { // TODO: unmarshal the response tx, err := unmarshalResponseNameReg(b) if err != nil { return err } if tx.Name != name { t.Fatal(fmt.Sprintf("Err on received event tx.Name: Got %s, expected %s", tx.Name, name)) } if tx.Data != data { t.Fatal(fmt.Sprintf("Err on received event tx.Data: Got %s, expected %s", tx.Data, data)) } return nil }) mempoolCount = 0 entry := getNameRegEntry(t, typ, name) if entry.Data != data { t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data)) } if bytes.Compare(entry.Owner, user[0].Address) != 0 { t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[0].Address)) } unsubscribe(t, con, eid) // for the rest we just use new block event // since we already tested the namereg event eid = types.EventStringNewBlock() subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) con.Close() }() // update the data as the owner, make sure still there numDesiredBlocks = int64(2) data = "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need" amt = fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx = makeDefaultNameTx(t, typ, name, data, amt, fee) broadcastTx(t, typ, tx) // commit block waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 entry = getNameRegEntry(t, typ, name) if entry.Data != data { t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data)) } // try to update as non owner, should fail nonce := getNonce(t, typ, user[1].Address) data2 := "this is not my beautiful house" tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1) tx.Sign(chainID, user[1]) _, err := client.BroadcastTx(tx) if err == nil { t.Fatal("Expected error on NameTx") } // commit block waitForEvent(t, con, eid, true, func() {}, doNothing) // now the entry should be expired, so we can update as non owner _, err = client.BroadcastTx(tx) waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 entry = getNameRegEntry(t, typ, name) if entry.Data != data2 { t.Fatal(fmt.Sprintf("Error on entry.Data: Got %s, expected %s", entry.Data, data2)) } if bytes.Compare(entry.Owner, user[1].Address) != 0 { t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[1].Address)) } }
// Increment height and goto RoundStepNewHeight func (cs *ConsensusState) finalizeCommit(height int) { if cs.Height != height || cs.Step != RoundStepCommit { log.Debug(Fmt("finalizeCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step)) return } hash, header, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts if !ok { PanicSanity(Fmt("Cannot finalizeCommit, commit does not have two thirds majority")) } if !blockParts.HasHeader(header) { PanicSanity(Fmt("Expected ProposalBlockParts header to be commit header")) } if !block.HashesTo(hash) { PanicSanity(Fmt("Cannot finalizeCommit, ProposalBlock does not hash to commit hash")) } if err := cs.state.ValidateBlock(block); err != nil { PanicConsensus(Fmt("+2/3 committed an invalid block: %v", err)) } log.Notice(Fmt("Finalizing commit of block with %d txs", block.NumTxs), "height", block.Height, "hash", block.Hash()) log.Info(Fmt("%v", block)) // Fire off event for new block. // TODO: Handle app failure. See #177 cs.evsw.FireEvent(types.EventStringNewBlock(), types.EventDataNewBlock{block}) // Create a copy of the state for staging stateCopy := cs.state.Copy() // Run the block on the State: // + update validator sets // + run txs on the proxyAppConn err := stateCopy.ExecBlock(cs.evsw, cs.proxyAppConn, block, blockParts.Header()) if err != nil { // TODO: handle this gracefully. PanicQ(Fmt("Exec failed for application")) } // Save to blockStore. if cs.blockStore.Height() < block.Height { commits := cs.Votes.Precommits(cs.CommitRound) seenValidation := commits.MakeValidation() cs.blockStore.SaveBlock(block, blockParts, seenValidation) } /* // Commit to proxyAppConn err = cs.proxyAppConn.CommitSync() if err != nil { // TODO: handle this gracefully. PanicQ(Fmt("Commit failed for application")) } */ // Save the state. stateCopy.Save() // Update mempool. cs.mempool.Update(block.Height, block.Txs) // NewHeightStep! cs.updateToState(stateCopy) // cs.StartTime is already set. // Schedule Round0 to start soon. cs.scheduleRound0(height + 1) // By here, // * cs.Height has been increment to height+1 // * cs.Step is now RoundStepNewHeight // * cs.StartTime is set to when we will start round0. return }