mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-10 12:01:18 +00:00
rpc: websocket events testing
This commit is contained in:
@ -73,11 +73,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
roundDuration0 = 10 * time.Second // The first round is 60 seconds long.
|
||||
roundDurationDelta = 3 * time.Second // Each successive round lasts 15 seconds longer.
|
||||
roundDeadlinePrevote = float64(1.0 / 3.0) // When the prevote is due.
|
||||
roundDeadlinePrecommit = float64(2.0 / 3.0) // When the precommit vote is due.
|
||||
newHeightDelta = roundDuration0 / 3 // The time to wait between commitTime and startTime of next consensus rounds.
|
||||
)
|
||||
|
||||
var (
|
||||
RoundDuration0 = 10 * time.Second // The first round is 60 seconds long.
|
||||
RoundDurationDelta = 3 * time.Second // Each successive round lasts 15 seconds longer.
|
||||
newHeightDelta = RoundDuration0 / 3 // The time to wait between commitTime and startTime of next consensus rounds.
|
||||
)
|
||||
|
||||
var (
|
||||
@ -318,14 +321,14 @@ func (cs *ConsensusState) stepTransitionRoutine() {
|
||||
// NOTE: We can push directly to runActionCh because
|
||||
// we're running in a separate goroutine, which avoids deadlocks.
|
||||
rs := cs.getRoundState()
|
||||
round, roundStartTime, roundDuration, _, elapsedRatio := calcRoundInfo(rs.StartTime)
|
||||
round, roundStartTime, RoundDuration, _, elapsedRatio := calcRoundInfo(rs.StartTime)
|
||||
log.Debug("Scheduling next action", "height", rs.Height, "round", round, "step", rs.Step, "roundStartTime", roundStartTime, "elapsedRatio", elapsedRatio)
|
||||
switch rs.Step {
|
||||
case RoundStepNewHeight:
|
||||
// We should run RoundActionPropose when rs.StartTime passes.
|
||||
if elapsedRatio < 0 {
|
||||
// startTime is in the future.
|
||||
time.Sleep(time.Duration((-1.0 * elapsedRatio) * float64(roundDuration)))
|
||||
time.Sleep(time.Duration((-1.0 * elapsedRatio) * float64(RoundDuration)))
|
||||
}
|
||||
cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPropose}
|
||||
case RoundStepNewRound:
|
||||
@ -333,15 +336,15 @@ func (cs *ConsensusState) stepTransitionRoutine() {
|
||||
cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPropose}
|
||||
case RoundStepPropose:
|
||||
// Wake up when it's time to vote.
|
||||
time.Sleep(time.Duration((roundDeadlinePrevote - elapsedRatio) * float64(roundDuration)))
|
||||
time.Sleep(time.Duration((roundDeadlinePrevote - elapsedRatio) * float64(RoundDuration)))
|
||||
cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPrevote}
|
||||
case RoundStepPrevote:
|
||||
// Wake up when it's time to precommit.
|
||||
time.Sleep(time.Duration((roundDeadlinePrecommit - elapsedRatio) * float64(roundDuration)))
|
||||
time.Sleep(time.Duration((roundDeadlinePrecommit - elapsedRatio) * float64(RoundDuration)))
|
||||
cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPrecommit}
|
||||
case RoundStepPrecommit:
|
||||
// Wake up when the round is over.
|
||||
time.Sleep(time.Duration((1.0 - elapsedRatio) * float64(roundDuration)))
|
||||
time.Sleep(time.Duration((1.0 - elapsedRatio) * float64(RoundDuration)))
|
||||
cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionTryCommit}
|
||||
case RoundStepCommit:
|
||||
// There's nothing to scheudle, we're waiting for
|
||||
@ -1122,13 +1125,13 @@ func (cs *ConsensusState) SetEventSwitch(evsw *events.EventSwitch) {
|
||||
|
||||
// total duration of given round
|
||||
func calcRoundDuration(round uint) time.Duration {
|
||||
return roundDuration0 + roundDurationDelta*time.Duration(round)
|
||||
return RoundDuration0 + RoundDurationDelta*time.Duration(round)
|
||||
}
|
||||
|
||||
// startTime is when round zero started.
|
||||
func calcRoundStartTime(round uint, startTime time.Time) time.Time {
|
||||
return startTime.Add(roundDuration0*time.Duration(round) +
|
||||
roundDurationDelta*(time.Duration((int64(round)*int64(round)-int64(round))/2)))
|
||||
return startTime.Add(RoundDuration0*time.Duration(round) +
|
||||
RoundDurationDelta*(time.Duration((int64(round)*int64(round)-int64(round))/2)))
|
||||
}
|
||||
|
||||
// calculates the current round given startTime of round zero.
|
||||
@ -1142,8 +1145,8 @@ func calcRound(startTime time.Time) uint {
|
||||
// D_delta * R^2 + (2D_0 - D_delta) * R + 2(Start - Now) <= 0.
|
||||
// AR^2 + BR + C <= 0; A = D_delta, B = (2_D0 - D_delta), C = 2(Start - Now).
|
||||
// R = Floor((-B + Sqrt(B^2 - 4AC))/2A)
|
||||
A := float64(roundDurationDelta)
|
||||
B := 2.0*float64(roundDuration0) - float64(roundDurationDelta)
|
||||
A := float64(RoundDurationDelta)
|
||||
B := 2.0*float64(RoundDuration0) - float64(RoundDurationDelta)
|
||||
C := 2.0 * float64(startTime.Sub(now))
|
||||
R := math.Floor((-B + math.Sqrt(B*B-4.0*A*C)) / (2 * A))
|
||||
if math.IsNaN(R) {
|
||||
@ -1160,12 +1163,12 @@ func calcRound(startTime time.Time) uint {
|
||||
|
||||
// convenience
|
||||
// NOTE: elapsedRatio can be negative if startTime is in the future.
|
||||
func calcRoundInfo(startTime time.Time) (round uint, roundStartTime time.Time, roundDuration time.Duration,
|
||||
func calcRoundInfo(startTime time.Time) (round uint, roundStartTime time.Time, RoundDuration time.Duration,
|
||||
roundElapsed time.Duration, elapsedRatio float64) {
|
||||
round = calcRound(startTime)
|
||||
roundStartTime = calcRoundStartTime(round, startTime)
|
||||
roundDuration = calcRoundDuration(round)
|
||||
RoundDuration = calcRoundDuration(round)
|
||||
roundElapsed = time.Now().Sub(roundStartTime)
|
||||
elapsedRatio = float64(roundElapsed) / float64(roundDuration)
|
||||
elapsedRatio = float64(roundElapsed) / float64(RoundDuration)
|
||||
return
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func (mem *Mempool) GetCache() *sm.BlockCache {
|
||||
func (mem *Mempool) AddTx(tx types.Tx) (err error) {
|
||||
mem.mtx.Lock()
|
||||
defer mem.mtx.Unlock()
|
||||
err = sm.ExecTx(mem.cache, tx, false)
|
||||
err = sm.ExecTx(mem.cache, tx, false, false)
|
||||
if err != nil {
|
||||
log.Debug("AddTx() error", "tx", tx, "error", err)
|
||||
return err
|
||||
@ -93,7 +93,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) {
|
||||
// Next, filter all txs that aren't valid given new state.
|
||||
validTxs := []types.Tx{}
|
||||
for _, tx := range txs {
|
||||
err := sm.ExecTx(mem.cache, tx, false)
|
||||
err := sm.ExecTx(mem.cache, tx, false, false)
|
||||
if err == nil {
|
||||
log.Debug("Filter in, valid", "tx", tx)
|
||||
validTxs = append(validTxs, tx)
|
||||
|
@ -1,10 +1,6 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/tendermint/tendermint/rpc"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -77,34 +73,3 @@ func TestJSONCallCode(t *testing.T) {
|
||||
func TestJSONCallContract(t *testing.T) {
|
||||
testCall(t, "JSONRPC")
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Test the websocket client
|
||||
|
||||
func TestWSConnect(t *testing.T) {
|
||||
dialer := websocket.DefaultDialer
|
||||
rHeader := http.Header{}
|
||||
_, r, err := dialer.Dial(websocketAddr, rHeader)
|
||||
fmt.Println("respoinse:", r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSSubscribe(t *testing.T) {
|
||||
dialer := websocket.DefaultDialer
|
||||
rHeader := http.Header{}
|
||||
con, _, err := dialer.Dial(websocketAddr, rHeader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = con.WriteJSON(rpc.WSRequest{
|
||||
Type: "subscribe",
|
||||
Event: "newblock",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
typ, p, err := con.ReadMessage()
|
||||
fmt.Println("RESPONSE:", typ, string(p), err)
|
||||
}
|
||||
|
327
rpc/test/client_ws_test.go
Normal file
327
rpc/test/client_ws_test.go
Normal file
@ -0,0 +1,327 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/tendermint/tendermint/binary"
|
||||
"github.com/tendermint/tendermint/rpc"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Utilities for testing the websocket service
|
||||
|
||||
// create a new connection
|
||||
func newWSCon(t *testing.T) *websocket.Conn {
|
||||
dialer := websocket.DefaultDialer
|
||||
rHeader := http.Header{}
|
||||
con, r, err := dialer.Dial(websocketAddr, rHeader)
|
||||
fmt.Println("response", r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return con
|
||||
}
|
||||
|
||||
// subscribe to an event
|
||||
func subscribe(t *testing.T, con *websocket.Conn, eventid string) {
|
||||
err := con.WriteJSON(rpc.WSRequest{
|
||||
Type: "subscribe",
|
||||
Event: eventid,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// unsubscribe from an event
|
||||
func unsubscribe(t *testing.T, con *websocket.Conn, eventid string) {
|
||||
err := con.WriteJSON(rpc.WSRequest{
|
||||
Type: "unsubscribe",
|
||||
Event: eventid,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for an event, do things that might trigger events, and check them when they are received
|
||||
func waitForEvent(t *testing.T, con *websocket.Conn, eventid string, dieOnTimeout bool, f func(), check func(string, []byte) error) {
|
||||
// go routine to wait for webscoket msg
|
||||
gch := make(chan []byte)
|
||||
ech := make(chan error)
|
||||
go func() {
|
||||
typ, p, err := con.ReadMessage()
|
||||
fmt.Println("RESPONSE:", typ, string(p), err)
|
||||
if err != nil {
|
||||
ech <- err
|
||||
} else {
|
||||
gch <- p
|
||||
}
|
||||
}()
|
||||
|
||||
// do stuff (transactions)
|
||||
f()
|
||||
|
||||
// if the event is not received in 20 seconds, die
|
||||
ticker := time.Tick(10 * time.Second)
|
||||
select {
|
||||
case <-ticker:
|
||||
if dieOnTimeout {
|
||||
con.Close()
|
||||
t.Fatalf("%s event was not received in time", eventid)
|
||||
}
|
||||
// else that's great, we didn't hear the event
|
||||
case p := <-gch:
|
||||
if dieOnTimeout {
|
||||
// message was received and expected
|
||||
// run the check
|
||||
err := check(eventid, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
con.Close()
|
||||
t.Fatalf("%s event was not expected", eventid)
|
||||
}
|
||||
case err := <-ech:
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalResponseNewBlock(b []byte) (*types.Block, error) {
|
||||
// unmarshall and assert somethings
|
||||
var response struct {
|
||||
Event string
|
||||
Data *types.Block
|
||||
Error string
|
||||
}
|
||||
var err error
|
||||
binary.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return nil, fmt.Errorf(response.Error)
|
||||
}
|
||||
block := response.Data
|
||||
return block, nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Test the websocket service
|
||||
|
||||
// make a simple connection to the server
|
||||
func TestWSConnect(t *testing.T) {
|
||||
con := newWSCon(t)
|
||||
con.Close()
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
// 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()
|
||||
}()
|
||||
var initBlockN uint
|
||||
for i := 0; i < 2; i++ {
|
||||
waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error {
|
||||
block, err := unmarshalResponseNewBlock(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i == 0 {
|
||||
initBlockN = block.Header.Height
|
||||
} else {
|
||||
if block.Header.Height != initBlockN+uint(i) {
|
||||
return fmt.Errorf("Expected block %d, got block %d", i, block.Header.Height)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// send a transaction and validate the events from listening for both sender and receiver
|
||||
func TestWSSend(t *testing.T) {
|
||||
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
|
||||
amt := uint64(100)
|
||||
|
||||
con := newWSCon(t)
|
||||
eidInput := types.EventStringAccInput(byteAddr)
|
||||
eidOutput := types.EventStringAccOutput(toAddr)
|
||||
subscribe(t, con, eidInput)
|
||||
subscribe(t, con, eidOutput)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eidInput)
|
||||
unsubscribe(t, con, eidOutput)
|
||||
con.Close()
|
||||
}()
|
||||
checkerFunc := func(eid string, b []byte) error {
|
||||
// unmarshal and assert correctness
|
||||
var response struct {
|
||||
Event string
|
||||
Data types.SendTx
|
||||
Error string
|
||||
}
|
||||
var err error
|
||||
binary.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf(response.Error)
|
||||
}
|
||||
if eid != response.Event {
|
||||
return fmt.Errorf("Eventid is not correct. Got %s, expected %s", response.Event, eid)
|
||||
}
|
||||
tx := response.Data
|
||||
if bytes.Compare(tx.Inputs[0].Address, byteAddr) != 0 {
|
||||
return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, byteAddr)
|
||||
}
|
||||
if tx.Inputs[0].Amount != amt {
|
||||
return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt)
|
||||
}
|
||||
if bytes.Compare(tx.Outputs[0].Address, toAddr) != 0 {
|
||||
return fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, byteAddr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
waitForEvent(t, con, eidInput, true, func() {
|
||||
broadcastTx(t, "JSONRPC", byteAddr, toAddr, nil, byteKey, amt, 0, 0)
|
||||
}, checkerFunc)
|
||||
waitForEvent(t, con, eidOutput, true, func() {}, checkerFunc)
|
||||
}
|
||||
|
||||
// ensure events are only fired once for a given transaction
|
||||
func TestWSDoubleFire(t *testing.T) {
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringAccInput(byteAddr)
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
amt := uint64(100)
|
||||
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
|
||||
// broadcast the transaction, wait to hear about it
|
||||
waitForEvent(t, con, eid, true, func() {
|
||||
broadcastTx(t, "JSONRPC", byteAddr, toAddr, nil, byteKey, amt, 0, 0)
|
||||
}, func(eid string, b []byte) error {
|
||||
return nil
|
||||
})
|
||||
// but make sure we don't hear about it twice
|
||||
waitForEvent(t, con, eid, false, func() {
|
||||
}, func(eid string, b []byte) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// create a contract and send it a msg, validate the return
|
||||
func TestWSCall(t *testing.T) {
|
||||
byteAddr, _ := hex.DecodeString(userAddr)
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringAccInput(byteAddr)
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
amt := uint64(10000)
|
||||
code, returnCode, returnVal := simpleCallContract()
|
||||
var contractAddr []byte
|
||||
// wait for the contract to be created
|
||||
waitForEvent(t, con, eid, true, func() {
|
||||
_, receipt := broadcastTx(t, "JSONRPC", byteAddr, nil, code, byteKey, amt, 1000, 1000)
|
||||
contractAddr = receipt.ContractAddr
|
||||
|
||||
}, func(eid string, b []byte) error {
|
||||
// unmarshall and assert somethings
|
||||
var response struct {
|
||||
Event string
|
||||
Data struct {
|
||||
Tx types.CallTx
|
||||
Return []byte
|
||||
Exception string
|
||||
}
|
||||
Error string
|
||||
}
|
||||
var err error
|
||||
binary.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf(response.Error)
|
||||
}
|
||||
if response.Data.Exception != "" {
|
||||
return fmt.Errorf(response.Data.Exception)
|
||||
}
|
||||
tx := response.Data.Tx
|
||||
if bytes.Compare(tx.Input.Address, byteAddr) != 0 {
|
||||
return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Input.Address, byteAddr)
|
||||
}
|
||||
if tx.Input.Amount != amt {
|
||||
return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Input.Amount, amt)
|
||||
}
|
||||
ret := response.Data.Return
|
||||
if bytes.Compare(ret, returnCode) != 0 {
|
||||
return fmt.Errorf("Create did not return correct byte code for new contract. Got %x, expected %x", ret, returnCode)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// get the return value from a call
|
||||
data := []byte{0x1} // just needs to be non empty for this to be a CallTx
|
||||
waitForEvent(t, con, eid, true, func() {
|
||||
broadcastTx(t, "JSONRPC", byteAddr, contractAddr, data, byteKey, amt, 1000, 1000)
|
||||
}, func(eid string, b []byte) error {
|
||||
// unmarshall and assert somethings
|
||||
var response struct {
|
||||
Event string
|
||||
Data struct {
|
||||
Tx types.CallTx
|
||||
Return []byte
|
||||
Exception string
|
||||
}
|
||||
Error string
|
||||
}
|
||||
var err error
|
||||
binary.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf(response.Error)
|
||||
}
|
||||
ret := response.Data.Return
|
||||
if bytes.Compare(ret, returnVal) != 0 {
|
||||
return fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnVal)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/consensus"
|
||||
"github.com/tendermint/tendermint/logger"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// global variables for use across all tests
|
||||
@ -26,9 +28,10 @@ var (
|
||||
|
||||
mempoolCount = 0
|
||||
|
||||
userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB"
|
||||
userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
|
||||
userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
|
||||
userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB"
|
||||
userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
|
||||
userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
|
||||
byteAddr, byteKey = initUserBytes()
|
||||
|
||||
clients = map[string]cclient.Client{
|
||||
"JSONRPC": cclient.NewClient(requestAddr, "JSONRPC"),
|
||||
@ -36,6 +39,14 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func initUserBytes() ([]byte, [64]byte) {
|
||||
byteAddr, _ := hex.DecodeString(userAddr)
|
||||
var byteKey [64]byte
|
||||
oh, _ := hex.DecodeString(userPriv)
|
||||
copy(byteKey[:], oh)
|
||||
return byteAddr, byteKey
|
||||
}
|
||||
|
||||
func decodeHex(hexStr string) []byte {
|
||||
bytes, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
@ -84,6 +95,9 @@ func init() {
|
||||
priv.SetFile(rootDir + "/priv_validator.json")
|
||||
priv.Save()
|
||||
|
||||
consensus.RoundDuration0 = 3 * time.Second
|
||||
consensus.RoundDurationDelta = 1 * time.Second
|
||||
|
||||
// start a node
|
||||
ready := make(chan struct{})
|
||||
go newNode(ready)
|
||||
@ -264,3 +278,20 @@ func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types
|
||||
t.Fatal(types.ErrTxInvalidSignature)
|
||||
}
|
||||
}
|
||||
|
||||
// simple contract returns 5 + 6 = 0xb
|
||||
func simpleCallContract() ([]byte, []byte, []byte) {
|
||||
// this is the code we want to run when the contract is called
|
||||
contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
|
||||
// the is the code we need to return the contractCode when the contract is initialized
|
||||
lenCode := len(contractCode)
|
||||
// push code to the stack
|
||||
//code := append([]byte{byte(0x60 + lenCode - 1)}, LeftPadWord256(contractCode).Bytes()...)
|
||||
code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...)
|
||||
// store it in memory
|
||||
code = append(code, []byte{0x60, 0x0, 0x52}...)
|
||||
// return whats in memory
|
||||
//code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...)
|
||||
code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...)
|
||||
return code, contractCode, LeftPadBytes([]byte{0xb}, 32)
|
||||
}
|
||||
|
@ -49,11 +49,6 @@ func testGetAccount(t *testing.T, typ string) {
|
||||
}
|
||||
|
||||
func testSignedTx(t *testing.T, typ string) {
|
||||
byteAddr, _ := hex.DecodeString(userAddr)
|
||||
var byteKey [64]byte
|
||||
oh, _ := hex.DecodeString(userPriv)
|
||||
copy(byteKey[:], oh)
|
||||
|
||||
amt := uint64(100)
|
||||
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
|
||||
tx, priv := signTx(t, typ, byteAddr, toAddr, nil, byteKey, amt, 0, 0)
|
||||
@ -69,11 +64,6 @@ func testSignedTx(t *testing.T, typ string) {
|
||||
}
|
||||
|
||||
func testBroadcastTx(t *testing.T, typ string) {
|
||||
byteAddr, _ := hex.DecodeString(userAddr)
|
||||
var byteKey [64]byte
|
||||
oh, _ := hex.DecodeString(userPriv)
|
||||
copy(byteKey[:], oh)
|
||||
|
||||
amt := uint64(100)
|
||||
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
|
||||
tx, receipt := broadcastTx(t, typ, byteAddr, toAddr, nil, byteKey, amt, 0, 0)
|
||||
@ -103,11 +93,6 @@ func testGetStorage(t *testing.T, typ string) {
|
||||
_ = priv
|
||||
//core.SetPrivValidator(priv)
|
||||
|
||||
byteAddr, _ := hex.DecodeString(userAddr)
|
||||
var byteKey [64]byte
|
||||
oh, _ := hex.DecodeString(userPriv)
|
||||
copy(byteKey[:], oh)
|
||||
|
||||
amt := uint64(1100)
|
||||
code := []byte{0x60, 0x5, 0x60, 0x1, 0x55}
|
||||
_, receipt := broadcastTx(t, typ, byteAddr, nil, code, byteKey, amt, 1000, 1000)
|
||||
@ -153,30 +138,9 @@ func testCallCode(t *testing.T, typ string) {
|
||||
func testCall(t *testing.T, typ string) {
|
||||
client := clients[typ]
|
||||
|
||||
priv := state.LoadPrivValidator(".tendermint/priv_validator.json")
|
||||
_ = priv
|
||||
//core.SetPrivValidator(priv)
|
||||
|
||||
byteAddr, _ := hex.DecodeString(userAddr)
|
||||
var byteKey [64]byte
|
||||
oh, _ := hex.DecodeString(userPriv)
|
||||
copy(byteKey[:], oh)
|
||||
|
||||
// create the contract
|
||||
amt := uint64(6969)
|
||||
// this is the code we want to run when the contract is called
|
||||
contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
|
||||
// the is the code we need to return the contractCode when the contract is initialized
|
||||
lenCode := len(contractCode)
|
||||
// push code to the stack
|
||||
//code := append([]byte{byte(0x60 + lenCode - 1)}, LeftPadWord256(contractCode).Bytes()...)
|
||||
code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...)
|
||||
// store it in memory
|
||||
code = append(code, []byte{0x60, 0x0, 0x52}...)
|
||||
// return whats in memory
|
||||
//code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...)
|
||||
code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...)
|
||||
|
||||
code, _, _ := simpleCallContract()
|
||||
_, receipt := broadcastTx(t, typ, byteAddr, nil, code, byteKey, amt, 1000, 1000)
|
||||
if receipt.CreatesContract == 0 {
|
||||
t.Fatal("This tx creates a contract")
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
// NOTE: If an error occurs during block execution, state will be left
|
||||
// at an invalid state. Copy the state before calling ExecBlock!
|
||||
func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
|
||||
err := execBlock(s, block, blockPartsHeader)
|
||||
err := execBlock(s, block, blockPartsHeader, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -29,7 +29,7 @@ func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
// executes transactions of a block, does not check block.StateHash
|
||||
// NOTE: If an error occurs during block execution, state will be left
|
||||
// at an invalid state. Copy the state before calling execBlock!
|
||||
func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
|
||||
func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader, fireEvents bool) error {
|
||||
// Basic block validation.
|
||||
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
|
||||
if err != nil {
|
||||
@ -111,7 +111,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
|
||||
// Commit each tx
|
||||
for _, tx := range block.Data.Txs {
|
||||
err := ExecTx(blockCache, tx, true)
|
||||
err := ExecTx(blockCache, tx, true, fireEvents)
|
||||
if err != nil {
|
||||
return InvalidTxError{tx, err}
|
||||
}
|
||||
@ -291,7 +291,7 @@ func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutpu
|
||||
|
||||
// If the tx is invalid, an error will be returned.
|
||||
// Unlike ExecBlock(), state will not be altered.
|
||||
func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall, fireEvents bool) error {
|
||||
|
||||
// TODO: do something with fees
|
||||
fees := uint64(0)
|
||||
@ -329,7 +329,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
// If we're in a block (not mempool),
|
||||
// fire event on all inputs and outputs
|
||||
// see types/events.go for spec
|
||||
if runCall {
|
||||
if fireEvents {
|
||||
for _, i := range tx.Inputs {
|
||||
_s.evsw.FireEvent(types.EventStringAccInput(i.Address), tx)
|
||||
}
|
||||
@ -449,19 +449,21 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
// Create a receipt from the ret and whether errored.
|
||||
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
|
||||
|
||||
// Fire Events for sender and receiver
|
||||
// a separate event will be fired from vm for each
|
||||
_s.evsw.FireEvent(types.EventStringAccInput(tx.Input.Address), struct {
|
||||
Tx types.Tx
|
||||
Return []byte
|
||||
Exception string
|
||||
}{tx, ret, exception})
|
||||
if fireEvents {
|
||||
// Fire Events for sender and receiver
|
||||
// a separate event will be fired from vm for each
|
||||
_s.evsw.FireEvent(types.EventStringAccInput(tx.Input.Address), struct {
|
||||
Tx types.Tx
|
||||
Return []byte
|
||||
Exception string
|
||||
}{tx, ret, exception})
|
||||
|
||||
_s.evsw.FireEvent(types.EventStringAccReceive(tx.Address), struct {
|
||||
Tx types.Tx
|
||||
Return []byte
|
||||
Exception string
|
||||
}{tx, ret, exception})
|
||||
_s.evsw.FireEvent(types.EventStringAccReceive(tx.Address), struct {
|
||||
Tx types.Tx
|
||||
Return []byte
|
||||
Exception string
|
||||
}{tx, ret, exception})
|
||||
}
|
||||
} else {
|
||||
// The mempool does not call txs until
|
||||
// the proposer determines the order of txs.
|
||||
@ -529,7 +531,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
if !added {
|
||||
panic("Failed to add validator")
|
||||
}
|
||||
if runCall {
|
||||
if fireEvents {
|
||||
_s.evsw.FireEvent(types.EventStringBond(), tx)
|
||||
}
|
||||
return nil
|
||||
@ -554,7 +556,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
|
||||
// Good!
|
||||
_s.unbondValidator(val)
|
||||
if runCall {
|
||||
if fireEvents {
|
||||
_s.evsw.FireEvent(types.EventStringUnbond(), tx)
|
||||
}
|
||||
return nil
|
||||
@ -579,7 +581,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
|
||||
// Good!
|
||||
_s.rebondValidator(val)
|
||||
if runCall {
|
||||
if fireEvents {
|
||||
_s.evsw.FireEvent(types.EventStringRebond(), tx)
|
||||
}
|
||||
return nil
|
||||
@ -625,7 +627,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
|
||||
|
||||
// Good! (Bad validator!)
|
||||
_s.destroyValidator(accused)
|
||||
if runCall {
|
||||
if fireEvents {
|
||||
_s.evsw.FireEvent(types.EventStringDupeout(), tx)
|
||||
}
|
||||
return nil
|
||||
|
@ -119,7 +119,7 @@ func (s *State) Hash() []byte {
|
||||
// Mutates the block in place and updates it with new state hash.
|
||||
func (s *State) SetBlockStateHash(block *types.Block) error {
|
||||
sCopy := s.Copy()
|
||||
err := execBlock(sCopy, block, types.PartSetHeader{})
|
||||
err := execBlock(sCopy, block, types.PartSetHeader{}, false) // don't fire events
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
8
vm/vm.go
8
vm/vm.go
@ -6,6 +6,7 @@ import (
|
||||
"math/big"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/tendermint/vm/sha3"
|
||||
)
|
||||
|
||||
@ -45,6 +46,8 @@ type VM struct {
|
||||
origin Word256
|
||||
|
||||
callDepth int
|
||||
|
||||
evsw *events.EventSwitch
|
||||
}
|
||||
|
||||
func NewVM(appState AppState, params Params, origin Word256) *VM {
|
||||
@ -56,6 +59,11 @@ func NewVM(appState AppState, params Params, origin Word256) *VM {
|
||||
}
|
||||
}
|
||||
|
||||
// satisfies events.Eventable
|
||||
func (vm *VM) SetEventSwitch(evsw *events.EventSwitch) {
|
||||
vm.evsw = evsw
|
||||
}
|
||||
|
||||
// CONTRACT appState is aware of caller and callee, so we can just mutate them.
|
||||
// value: To be transferred from caller to callee. Refunded upon error.
|
||||
// gas: Available gas. No refunds for gas.
|
||||
|
Reference in New Issue
Block a user