From 4001464d8f5547d0596913f6b37cbe0a7d08bbc8 Mon Sep 17 00:00:00 2001 From: Alexey Kiselev Date: Sat, 28 Dec 2024 19:26:18 +0400 Subject: [PATCH] Itest light node state hash (#1512) * Test fixture that starts one Go node added. Configuration option to disable mining on Go node added. Configuration option to enable mining on Scala node converted to the option that disables mining on it. Existing tests changed accordingly. * Genesis and key-block snapshot hashes generation added. Messages sequence implemented. Score calculation added. * Added config options to configure preactivated featuers and absence period. * Fix genesis block snapshot hash calculation. Added delay before sending key-block. * Test refactoring. * Linter issues fixed. * Rename test suite. * Better function naming. Constant extracted. * TaskGroup goroutine manager added in pkg/execution package. Tests on TaskGroup added. * Networking package with a new connection handler Session added. * Logger interface removed from networking package. Standard slog package is used instead. * WIP. Simple connection replaced with NetClient. NetClient usage moved into Universal client. Handshake proto updated to compatibility with Handshake interface from networking package. * Fixed NetClient closing issue. Configuration option to set KeepAliveInterval added to networking.Config. * Redundant log removed. * Move save int conversion to safecast lib. * Accidentially added files removed. * Fixed handshake issue for the single node test suite. * Fix data race error in 'networking_test' package Implement 'io.Stringer' for 'Session' struct. Data race happens because 'clientHandler' mock in 'TestSessionTimeoutOnHandshake' test reads 'Session' structure at the same time as 'clientSession.Close' call. * Refactofing of test containers shutdown. Explicit test containers removal. Quorum incrased to 2 for itest nodes because of constant test network connection. Level of Session logging set to INFO for itests. * Paralles start and shutdown of test nodes implemented. * Add parallelizm to some Docker functions. * Support native itest container building for Gowaves node. * Fix linter issues. * Replace atomic.Uint32 with atomic.Bool and use CompareAndSwap there it's possible. Replace random delay with constan to make test not blink. Simplify assertion in test to make it stable. * Assertions added. Style fixed. * Simplified closing and close logic in NetClient. Added logs on handshake rejection to clarify the reason of rejections. Added and used function to configure Session with list of Slog attributes. * Prepare for new timer in Go 1.23 Co-authored-by: Nikolay Eskov * Move constant into function were it used. Proper error declaration. * Better way to prevent from running multiple receiveLoops. Shutdown lock replaced with sync.Once. * Better data emptyness checks. * Better read error handling. Co-authored-by: Nikolay Eskov * Use constructor. * Wrap heavy logging into log level checks. Fix data lock and data access order. * Session configuration accepts slog handler to set up logging. Discarding slog handler implemented and used instead of setting default slog logger. Checks on interval values added to Session constructor. * Close error channel on sending data successfully. Better error channel passing. Reset receiving buffer by deffering. * Better error handling while reading. Co-authored-by: Nikolay Eskov * Fine error assertions. * Fix blinking test. * Better configuration handling. Co-authored-by: Nikolay Eskov * Fixed blinking test TestCloseParentContext. Wait group added to wait for client to finish sending handshake. Better wait groups naming. * Better test workflow. Better wait group naming. * Fix deadlock in test by introducing wait group instead of sleep. * Function to disable IPv6 in itest docker containers added. Healthcheck instruction added to itest docker container. * Functions to subscribe and wait for network messages of specifed types added to itest network client. SimpleSnapshot test reimplemented using assertions for expected messages instead of sleeping for some time. * Better error messages. Close of handler's channel added. Unnecessary sleep removed. * Internal sendPacket reimplemented using io.Reader. Data restoration function removed. Handler's OnReceive use io.Reader to pass received data. Tests updated. Mocks regenerated. * Handler implementation updated. * Itest network client handler updated. * Fix multiple containers removal issue. --------- Co-authored-by: Nikolay Eskov --- Dockerfile.gowaves-it | 10 +- Makefile | 2 + itests/clients/net_client.go | 132 ++++++++- itests/clients/node_client.go | 10 +- itests/clients/universal_client.go | 17 ++ itests/config/blockchain.go | 88 +----- itests/config/blockchain_options.go | 98 +++++++ itests/config/config.go | 46 +++ itests/config/template.conf | 4 +- itests/docker/docker.go | 135 +++++++-- itests/fixtures/base_fixtures.go | 13 +- itests/fixtures/lite_node_feature_fixture.go | 19 -- .../reward_preactivated_features_fixtures.go | 20 -- .../reward_supported_features_fixtures.go | 22 -- itests/fixtures/single_go_node_suite.go | 69 +++++ itests/init_internal_test.go | 7 +- itests/snapshot_internal_test.go | 262 ++++++++++++++++++ .../lite_node_feature_fixture.json | 22 -- itests/utilities/common.go | 102 +++++-- 19 files changed, 840 insertions(+), 238 deletions(-) create mode 100644 itests/config/blockchain_options.go delete mode 100644 itests/fixtures/lite_node_feature_fixture.go create mode 100644 itests/fixtures/single_go_node_suite.go create mode 100644 itests/snapshot_internal_test.go delete mode 100644 itests/testdata/feature_settings/base_feature_settings/lite_node_feature_fixture.json diff --git a/Dockerfile.gowaves-it b/Dockerfile.gowaves-it index 372db228e..43cf12b60 100644 --- a/Dockerfile.gowaves-it +++ b/Dockerfile.gowaves-it @@ -14,13 +14,13 @@ COPY cmd cmd COPY pkg pkg ARG WITH_RACE_SUFFIX="" -RUN make build-node-linux-amd64${WITH_RACE_SUFFIX} +RUN make build-node-native${WITH_RACE_SUFFIX} FROM alpine:3.21.0 ENV TZ=Etc/UTC \ APP_USER=gowaves -RUN apk add --no-cache bind-tools +RUN apk add --no-cache bind-tools curl RUN addgroup -S $APP_USER \ && adduser -S $APP_USER -G $APP_USER @@ -34,7 +34,9 @@ ENV CONFIG_PATH=/home/gowaves/config/gowaves-it.json \ USER $APP_USER -COPY --from=builder /app/build/bin/linux-amd64/node /app/node +COPY --from=builder /app/build/bin/native/node /app/node + +HEALTHCHECK CMD curl -f http://localhost:6869/node/status || exit 1 STOPSIGNAL SIGINT @@ -64,3 +66,5 @@ CMD /app/node \ -microblock-interval 2s \ -blacklist-residence-time 0 \ -rate-limiter-opts="rps=100&burst=100" \ + -min-peers-mining=2 \ + -disable-miner=$DISABLE_MINER \ diff --git a/Makefile b/Makefile index 55dd7feb7..0bd557ab0 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,8 @@ dist-blockcmp: release-blockcmp build-node-native: @go build -o build/bin/native/node -ldflags="-X 'github.com/wavesplatform/gowaves/pkg/versioning.Version=$(VERSION)'" ./cmd/node +build-node-native-with-race: + @go build -race -o build/bin/native/node -ldflags="-X 'github.com/wavesplatform/gowaves/pkg/versioning.Version=$(VERSION)'" ./cmd/node build-node-linux-amd64: @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/bin/linux-amd64/node -ldflags="-X 'github.com/wavesplatform/gowaves/pkg/versioning.Version=$(VERSION)'" ./cmd/node build-node-linux-amd64-with-race: diff --git a/itests/clients/net_client.go b/itests/clients/net_client.go index e6d984adb..f38c0657b 100644 --- a/itests/clients/net_client.go +++ b/itests/clients/net_client.go @@ -4,9 +4,13 @@ import ( "bytes" "context" "encoding/base64" + "errors" + "fmt" "io" "log/slog" + "math/big" "net" + "reflect" "sync" "sync/atomic" "testing" @@ -33,6 +37,7 @@ type NetClient struct { impl Implementation n *networking.Network c *networking.Config + h *handler s *networking.Session closing atomic.Bool @@ -45,7 +50,17 @@ func NewNetClient( n := networking.NewNetwork() p := newProtocol(t, nil) h := newHandler(t, peers) - log := slogt.New(t) + + f := slogt.Factory(func(w io.Writer) slog.Handler { + opts := &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelInfo, + } + return slog.NewTextHandler(w, opts) + }) + log := slogt.New(t, f) + + slog.SetLogLoggerLevel(slog.LevelError) conf := networking.NewConfig(p, h). WithSlogHandler(log.Handler()). WithWriteTimeout(networkTimeout). @@ -58,7 +73,7 @@ func NewNetClient( s, err := n.NewSession(ctx, conn, conf) require.NoError(t, err, "failed to establish new session to %s node", impl.String()) - cli := &NetClient{ctx: ctx, t: t, impl: impl, n: n, c: conf, s: s} + cli := &NetClient{ctx: ctx, t: t, impl: impl, n: n, c: conf, h: h, s: s} h.client = cli // Set client reference in handler. return cli } @@ -93,9 +108,85 @@ func (c *NetClient) Close() { } err := c.s.Close() require.NoError(c.t, err, "failed to close session to %s node at %q", c.impl.String(), c.s.RemoteAddr()) + c.h.close() }) } +// SubscribeForMessages adds specified types to the message waiting queue. +// Once the awaited message received the corresponding type is removed from the queue. +func (c *NetClient) SubscribeForMessages(messageType ...reflect.Type) error { + for _, mt := range messageType { + if err := c.h.waitFor(mt); err != nil { + return err + } + } + return nil +} + +// AwaitMessage waits for a message from the node for the specified timeout. +func (c *NetClient) AwaitMessage(messageType reflect.Type, timeout time.Duration) (proto.Message, error) { + select { + case <-c.ctx.Done(): + return nil, c.ctx.Err() + case <-time.After(timeout): + return nil, fmt.Errorf("timeout waiting for message of type %q", messageType.String()) + case msg := <-c.h.receiveChan(): + if reflect.TypeOf(msg) != messageType { + return nil, fmt.Errorf("unexpected message type %q, expecting %q", + reflect.TypeOf(msg).String(), messageType.String()) + } + return msg, nil + } +} + +// AwaitGetBlockMessage waits for a GetBlockMessage from the node for the specified timeout and +// returns the requested block ID. +func (c *NetClient) AwaitGetBlockMessage(timeout time.Duration) (proto.BlockID, error) { + msg, err := c.AwaitMessage(reflect.TypeOf(&proto.GetBlockMessage{}), timeout) + if err != nil { + return proto.BlockID{}, err + } + getBlockMessage, ok := msg.(*proto.GetBlockMessage) + if !ok { + return proto.BlockID{}, fmt.Errorf("failed to cast message of type %q to GetBlockMessage", + reflect.TypeOf(msg).String()) + } + return getBlockMessage.BlockID, nil +} + +// AwaitScoreMessage waits for a ScoreMessage from the node for the specified timeout and returns the received score. +func (c *NetClient) AwaitScoreMessage(timeout time.Duration) (*big.Int, error) { + msg, err := c.AwaitMessage(reflect.TypeOf(&proto.ScoreMessage{}), timeout) + if err != nil { + return nil, err + } + scoreMessage, ok := msg.(*proto.ScoreMessage) + if !ok { + return nil, fmt.Errorf("failed to cast message of type %q to ScoreMessage", reflect.TypeOf(msg).String()) + } + score := new(big.Int).SetBytes(scoreMessage.Score) + return score, nil +} + +// AwaitMicroblockRequest waits for a MicroBlockRequestMessage from the node for the specified timeout and +// returns the received block ID. +func (c *NetClient) AwaitMicroblockRequest(timeout time.Duration) (proto.BlockID, error) { + msg, err := c.AwaitMessage(reflect.TypeOf(&proto.MicroBlockRequestMessage{}), timeout) + if err != nil { + return proto.BlockID{}, err + } + mbr, ok := msg.(*proto.MicroBlockRequestMessage) + if !ok { + return proto.BlockID{}, fmt.Errorf("failed to cast message of type %q to MicroBlockRequestMessage", + reflect.TypeOf(msg).String()) + } + r, err := proto.NewBlockIDFromBytes(mbr.TotalBlockSig) + if err != nil { + return proto.BlockID{}, err + } + return r, nil +} + func (c *NetClient) reconnect() { c.t.Logf("Reconnecting to %q", c.s.RemoteAddr().String()) conn, err := net.Dial("tcp", c.s.RemoteAddr().String()) @@ -174,10 +265,13 @@ type handler struct { peers []proto.PeerInfo t testing.TB client *NetClient + queue []reflect.Type + ch chan proto.Message } func newHandler(t testing.TB, peers []proto.PeerInfo) *handler { - return &handler{t: t, peers: peers} + ch := make(chan proto.Message, 1) + return &handler{t: t, peers: peers, ch: ch} } func (h *handler) OnReceive(s *networking.Session, r io.Reader) { @@ -195,7 +289,6 @@ func (h *handler) OnReceive(s *networking.Session, r io.Reader) { } switch msg.(type) { // Only reply with peers on GetPeersMessage. case *proto.GetPeersMessage: - h.t.Logf("Received GetPeersMessage from %q", s.RemoteAddr()) rpl := &proto.PeersMessage{Peers: h.peers} if _, sErr := rpl.WriteTo(s); sErr != nil { h.t.Logf("Failed to send peers message: %v", sErr) @@ -203,6 +296,15 @@ func (h *handler) OnReceive(s *networking.Session, r io.Reader) { return } default: + if len(h.queue) == 0 { // No messages to wait for. + return + } + et := h.queue[0] + if reflect.TypeOf(msg) == et { + h.t.Logf("Received expected message of type %q", reflect.TypeOf(msg).String()) + h.queue = h.queue[1:] // Pop the expected type. + h.ch <- msg + } } } @@ -216,3 +318,25 @@ func (h *handler) OnClose(s *networking.Session) { h.client.reconnect() } } + +func (h *handler) waitFor(messageType reflect.Type) error { + if messageType == nil { + return errors.New("nil message type") + } + if messageType == reflect.TypeOf(proto.GetPeersMessage{}) { + return errors.New("cannot wait for GetPeersMessage") + } + h.queue = append(h.queue, messageType) + return nil +} + +func (h *handler) receiveChan() <-chan proto.Message { + return h.ch +} + +func (h *handler) close() { + if h.ch != nil { + close(h.ch) + h.ch = nil + } +} diff --git a/itests/clients/node_client.go b/itests/clients/node_client.go index 95a54d046..1e6002694 100644 --- a/itests/clients/node_client.go +++ b/itests/clients/node_client.go @@ -43,13 +43,13 @@ func NewNodesClients(ctx context.Context, t *testing.T, goPorts, scalaPorts *d.P } func (c *NodesClients) SendStartMessage(t *testing.T) { - c.GoClient.HTTPClient.PrintMsg(t, "------------- Start test: "+t.Name()+" -------------") - c.ScalaClient.HTTPClient.PrintMsg(t, "------------- Start test: "+t.Name()+" -------------") + c.GoClient.SendStartMessage(t) + c.ScalaClient.SendStartMessage(t) } func (c *NodesClients) SendEndMessage(t *testing.T) { - c.GoClient.HTTPClient.PrintMsg(t, "------------- End test: "+t.Name()+" -------------") - c.ScalaClient.HTTPClient.PrintMsg(t, "------------- End test: "+t.Name()+" -------------") + c.GoClient.SendEndMessage(t) + c.ScalaClient.SendEndMessage(t) } func (c *NodesClients) StateHashCmp(t *testing.T, height uint64) (*proto.StateHash, *proto.StateHash, bool) { @@ -242,7 +242,7 @@ func (c *NodesClients) SynchronizedWavesBalances( ctx, cancel := context.WithTimeout(context.Background(), synchronizedBalancesTimeout) defer cancel() - t.Logf("Initial balacnces request") + t.Logf("Initial balances request") sbs, err := c.requestAvailableBalancesForAddresses(ctx, addresses) if err != nil { t.Logf("Errors while requesting balances: %v", err) diff --git a/itests/clients/universal_client.go b/itests/clients/universal_client.go index 32c20833a..a8fae5ec2 100644 --- a/itests/clients/universal_client.go +++ b/itests/clients/universal_client.go @@ -24,3 +24,20 @@ func NewNodeUniversalClient( Connection: NewNetClient(ctx, t, impl, netPort, peers), } } + +func (c *NodeUniversalClient) SendStartMessage(t *testing.T) { + c.HTTPClient.PrintMsg(t, "------------- Start test: "+t.Name()+" -------------") +} + +func (c *NodeUniversalClient) SendEndMessage(t *testing.T) { + c.HTTPClient.PrintMsg(t, "------------- End test: "+t.Name()+" -------------") +} + +func (c *NodeUniversalClient) Handshake() { + c.Connection.SendHandshake() +} + +func (c *NodeUniversalClient) Close(t testing.TB) { + c.GRPCClient.Close(t) + c.Connection.Close() +} diff --git a/itests/config/blockchain.go b/itests/config/blockchain.go index 0c2c73553..ebe2e0daf 100644 --- a/itests/config/blockchain.go +++ b/itests/config/blockchain.go @@ -61,21 +61,19 @@ func (ra *RewardAddresses) AddressesAfter21() []proto.WavesAddress { return []proto.WavesAddress{} } -// BlockchainOption is a function type that allows to set additional parameters to BlockchainConfig. -type BlockchainOption func(*BlockchainConfig) error - // BlockchainConfig is a struct that contains settings for blockchain. // This configuration is used both for building Scala and Go configuration files. // Also, it's used to produce a Docker container run configurations for both nodes. type BlockchainConfig struct { - accounts []AccountInfo - supported []int16 - desiredReward uint64 - - Settings *settings.BlockchainSettings - Features []FeatureInfo - RewardAddresses RewardAddresses - EnableScalaMining bool + accounts []AccountInfo + supported []int16 + desiredReward uint64 + disableGoMining bool + disableScalaMining bool + + Settings *settings.BlockchainSettings + Features []FeatureInfo + RewardAddresses RewardAddresses } func NewBlockchainConfig(options ...BlockchainOption) (*BlockchainConfig, error) { @@ -174,71 +172,15 @@ func (c *BlockchainConfig) TestConfig() TestConfig { } } -// WithFeatureSettingFromFile is a BlockchainOption that allows to set feature settings from configuration file. -// Feature settings configuration file is a JSON file with the structure of `featureSettings`. -func WithFeatureSettingFromFile(path ...string) BlockchainOption { - return func(cfg *BlockchainConfig) error { - fs, err := NewFeatureSettingsFromFile(path...) - if err != nil { - return errors.Wrap(err, "failed to modify features settings") - } - cfg.supported = fs.SupportedFeatures - if ftErr := cfg.UpdatePreactivatedFeatures(fs.PreactivatedFeatures); ftErr != nil { - return errors.Wrap(ftErr, "failed to modify preactivated features") - } - return nil - } -} - -// WithPaymentsSettingFromFile is a BlockchainOption that allows to set payment settings from configuration file. -// Payment settings configuration file is a JSON file with the structure of `paymentSettings`. -func WithPaymentsSettingFromFile(path ...string) BlockchainOption { - return func(cfg *BlockchainConfig) error { - fs, err := NewPaymentSettingsFromFile(path...) - if err != nil { - return errors.Wrap(err, "failed to modify payments settings") - } - cfg.Settings.PaymentsFixAfterHeight = fs.PaymentsFixAfterHeight - cfg.Settings.InternalInvokePaymentsValidationAfterHeight = fs.InternalInvokePaymentsValidationAfterHeight - cfg.Settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight = - fs.InternalInvokeCorrectFailRejectBehaviourAfterHeight - cfg.Settings.InvokeNoZeroPaymentsAfterHeight = fs.InvokeNoZeroPaymentsAfterHeight - return nil - } -} - -// WithRewardSettingFromFile is a BlockchainOption that allows to set reward settings from configuration file. -// Reward settings configuration file is a JSON file with the structure of `rewardSettings`. -func WithRewardSettingFromFile(path ...string) BlockchainOption { - return func(cfg *BlockchainConfig) error { - rs, err := NewRewardSettingsFromFile(path...) - if err != nil { - return errors.Wrap(err, "failed to modify reward settings") - } - cfg.Settings.InitialBlockReward = rs.InitialBlockReward - cfg.Settings.BlockRewardIncrement = rs.BlockRewardIncrement - cfg.Settings.BlockRewardVotingPeriod = rs.BlockRewardVotingPeriod - cfg.Settings.BlockRewardTermAfter20 = rs.BlockRewardTermAfter20 - cfg.Settings.BlockRewardTerm = rs.BlockRewardTerm - cfg.Settings.MinXTNBuyBackPeriod = rs.MinXTNBuyBackPeriod - - ras, err := NewRewardAddresses(rs.DaoAddress, rs.XtnBuybackAddress) - if err != nil { - return errors.Wrap(err, "failed to modify reward settings") - } - cfg.RewardAddresses = ras - cfg.Settings.RewardAddresses = ras.Addresses() - cfg.Settings.RewardAddressesAfter21 = ras.AddressesAfter21() - cfg.desiredReward = rs.DesiredBlockReward - return nil - } +func (c *BlockchainConfig) DisableGoMiningString() string { + return strconv.FormatBool(c.disableGoMining) } -func WithScalaMining() BlockchainOption { - return func(cfg *BlockchainConfig) error { - cfg.EnableScalaMining = true - return nil +func (c *BlockchainConfig) EnableScalaMiningString() string { + if c.disableScalaMining { + return "no" } + return "yes" } func safeNow() uint64 { diff --git a/itests/config/blockchain_options.go b/itests/config/blockchain_options.go new file mode 100644 index 000000000..7b690fbbe --- /dev/null +++ b/itests/config/blockchain_options.go @@ -0,0 +1,98 @@ +package config + +import "github.com/pkg/errors" + +// BlockchainOption is a function type that allows to set additional parameters to BlockchainConfig. +type BlockchainOption func(*BlockchainConfig) error + +// WithRewardSettingFromFile is a BlockchainOption that allows to set reward settings from configuration file. +// Reward settings configuration file is a JSON file with the structure of `rewardSettings`. +func WithRewardSettingFromFile(path ...string) BlockchainOption { + return func(cfg *BlockchainConfig) error { + rs, err := NewRewardSettingsFromFile(path...) + if err != nil { + return errors.Wrap(err, "failed to modify reward settings") + } + cfg.Settings.InitialBlockReward = rs.InitialBlockReward + cfg.Settings.BlockRewardIncrement = rs.BlockRewardIncrement + cfg.Settings.BlockRewardVotingPeriod = rs.BlockRewardVotingPeriod + cfg.Settings.BlockRewardTermAfter20 = rs.BlockRewardTermAfter20 + cfg.Settings.BlockRewardTerm = rs.BlockRewardTerm + cfg.Settings.MinXTNBuyBackPeriod = rs.MinXTNBuyBackPeriod + + ras, err := NewRewardAddresses(rs.DaoAddress, rs.XtnBuybackAddress) + if err != nil { + return errors.Wrap(err, "failed to modify reward settings") + } + cfg.RewardAddresses = ras + cfg.Settings.RewardAddresses = ras.Addresses() + cfg.Settings.RewardAddressesAfter21 = ras.AddressesAfter21() + cfg.desiredReward = rs.DesiredBlockReward + return nil + } +} + +// WithFeatureSettingFromFile is a BlockchainOption that allows to set feature settings from configuration file. +// Feature settings configuration file is a JSON file with the structure of `featureSettings`. +func WithFeatureSettingFromFile(path ...string) BlockchainOption { + return func(cfg *BlockchainConfig) error { + fs, err := NewFeatureSettingsFromFile(path...) + if err != nil { + return errors.Wrap(err, "failed to modify features settings") + } + cfg.supported = fs.SupportedFeatures + if ftErr := cfg.UpdatePreactivatedFeatures(fs.PreactivatedFeatures); ftErr != nil { + return errors.Wrap(ftErr, "failed to modify preactivated features") + } + return nil + } +} + +// WithPaymentsSettingFromFile is a BlockchainOption that allows to set payment settings from configuration file. +// Payment settings configuration file is a JSON file with the structure of `paymentSettings`. +func WithPaymentsSettingFromFile(path ...string) BlockchainOption { + return func(cfg *BlockchainConfig) error { + fs, err := NewPaymentSettingsFromFile(path...) + if err != nil { + return errors.Wrap(err, "failed to modify payments settings") + } + cfg.Settings.PaymentsFixAfterHeight = fs.PaymentsFixAfterHeight + cfg.Settings.InternalInvokePaymentsValidationAfterHeight = fs.InternalInvokePaymentsValidationAfterHeight + cfg.Settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight = + fs.InternalInvokeCorrectFailRejectBehaviourAfterHeight + cfg.Settings.InvokeNoZeroPaymentsAfterHeight = fs.InvokeNoZeroPaymentsAfterHeight + return nil + } +} + +// WithNoScalaMining disables mining on the Scala node. +func WithNoScalaMining() BlockchainOption { + return func(cfg *BlockchainConfig) error { + cfg.disableScalaMining = true + return nil + } +} + +// WithNoGoMining disables mining on the Go node. +func WithNoGoMining() BlockchainOption { + return func(cfg *BlockchainConfig) error { + cfg.disableGoMining = true + return nil + } +} + +func WithPreactivatedFeatures(features []FeatureInfo) BlockchainOption { + return func(cfg *BlockchainConfig) error { + if ftErr := cfg.UpdatePreactivatedFeatures(features); ftErr != nil { + return errors.Wrap(ftErr, "failed to modify preactivated features") + } + return nil + } +} + +func WithAbsencePeriod(period uint64) BlockchainOption { + return func(cfg *BlockchainConfig) error { + cfg.Settings.LightNodeBlockFieldsAbsenceInterval = period + return nil + } +} diff --git a/itests/config/config.go b/itests/config/config.go index 8d885d927..7a287e10f 100644 --- a/itests/config/config.go +++ b/itests/config/config.go @@ -1,6 +1,7 @@ package config import ( + "encoding/binary" "encoding/json" stderrs "errors" "fmt" @@ -11,6 +12,8 @@ import ( "github.com/ory/dockertest/v3" "github.com/pkg/errors" + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" "github.com/wavesplatform/gowaves/pkg/settings" ) @@ -40,6 +43,48 @@ type TestConfig struct { BlockchainSettings *settings.BlockchainSettings } +func (c *TestConfig) GetRichestAccount() AccountInfo { + r := c.Accounts[0] + for _, a := range c.Accounts { + if a.Amount > r.Amount { + r = a + } + } + return r +} + +func (c *TestConfig) GenesisSH() crypto.Digest { + const uint64Size = 8 + + hash, err := crypto.NewFastHash() + if err != nil { + panic(err) + } + var emptyDigest crypto.Digest + hash.Sum(emptyDigest[:0]) + + // Create binary entries for all genesis transactions. + prevSH := emptyDigest + for _, a := range c.Accounts { + hash.Reset() + buf := make([]byte, proto.WavesAddressSize+uint64Size) + copy(buf, a.Address[:]) + binary.BigEndian.PutUint64(buf[proto.WavesAddressSize:], a.Amount) + hash.Write(buf) + var txSH crypto.Digest + hash.Sum(txSH[:0]) + + hash.Reset() + hash.Write(prevSH[:]) + hash.Write(txSH[:]) + + var newSH crypto.Digest + hash.Sum(newSH[:0]) + prevSH = newSH + } + return prevSH +} + type DockerConfigurator interface { DockerRunOptions() *dockertest.RunOptions } @@ -166,6 +211,7 @@ func (c *GoConfigurator) DockerRunOptions() *dockertest.RunOptions { "WALLET_PASSWORD=itest", "DESIRED_REWARD=" + c.cfg.DesiredBlockRewardString(), "SUPPORTED_FEATURES=" + c.cfg.SupportedFeaturesString(), + "DISABLE_MINER=" + c.cfg.DisableGoMiningString(), }, ExposedPorts: []string{ GRPCAPIPort + NetTCP, diff --git a/itests/config/template.conf b/itests/config/template.conf index b252c1831..71b58b02f 100644 --- a/itests/config/template.conf +++ b/itests/config/template.conf @@ -77,8 +77,8 @@ waves { rewards.desired = {{.DesiredBlockRewardString}} matcher.enable = no miner { - enable = {{if .EnableScalaMining}}yes{{else}}no{{end}} - quorum = 1 + enable = {{.EnableScalaMiningString}} + quorum = 2 interval-after-last-block-then-generation-is-allowed = 1h micro-block-interval = 2s min-micro-block-age = 0s diff --git a/itests/docker/docker.go b/itests/docker/docker.go index 5ed1b624d..326c1ad4a 100644 --- a/itests/docker/docker.go +++ b/itests/docker/docker.go @@ -14,13 +14,16 @@ import ( "github.com/ory/dockertest/v3" dc "github.com/ory/dockertest/v3/docker" "github.com/pkg/errors" + "golang.org/x/sync/errgroup" "github.com/wavesplatform/gowaves/itests/config" "github.com/wavesplatform/gowaves/pkg/client" ) const ( - DefaultTimeout = 16 * time.Second + DefaultTimeout = 16 * time.Second + PoolRetryTimeout = 2 * time.Minute + DefaultAPIKey = "itest-api-key" networkName = "waves-it-network" goNodeLogFileName = "go-node.log" @@ -71,6 +74,24 @@ func (c *NodeContainer) closeFiles() error { return err } +// Close purges container and closes log files. +func (c *NodeContainer) Close() error { + if c.container == nil { + return nil + } + if dcErr := c.container.DisconnectFromNetwork(c.network); dcErr != nil { + return errors.Wrapf(dcErr, "failed to disconnect container %q from network %q", + c.container.Container.ID, c.network.Network.Name) + } + if clErr := c.container.Close(); clErr != nil { + return errors.Wrapf(clErr, "failed to close container %q", c.container.Container.ID) + } + if err := c.closeFiles(); err != nil { + return err + } + return nil +} + type Docker struct { suite string @@ -90,6 +111,7 @@ func NewDocker(suiteName string) (*Docker, error) { if err != nil { return nil, err } + pool.MaxWait = PoolRetryTimeout docker := &Docker{suite: suiteName, pool: pool} if rmErr := docker.removeContainers(); rmErr != nil { return nil, rmErr @@ -114,6 +136,23 @@ func (d *Docker) ScalaNode() *NodeContainer { return d.scalaNode } +// StartNodes start both Go and Scala nodes with the given configurations. +// Note that while starting nodes in parallel it is impossible to retrieve the IP address of the other node in prior. +// So this method is heavily dependent on Docker DNS resolution and Go-node's domain name should be passed to the +// configuration of Scala node before calling this method: +// +// scalaConfigurator.WithGoNode("go-node") +func (d *Docker) StartNodes(ctx context.Context, goCfg, scalaCfg config.DockerConfigurator) error { + eg := errgroup.Group{} + eg.Go(func() error { + return d.StartGoNode(ctx, goCfg) + }) + eg.Go(func() error { + return d.StartScalaNode(ctx, scalaCfg) + }) + return eg.Wait() +} + // StartGoNode starts a Go node container with the given configuration. func (d *Docker) StartGoNode(ctx context.Context, cfg config.DockerConfigurator) error { var err error @@ -135,46 +174,60 @@ func (d *Docker) StartScalaNode(ctx context.Context, cfg config.DockerConfigurat } func (d *Docker) Finish(cancel context.CancelFunc) { + eg := errgroup.Group{} if d.scalaNode != nil { - err := d.pool.Client.KillContainer(dc.KillContainerOptions{ - ID: d.scalaNode.container.Container.ID, - Signal: dc.SIGINT, + eg.Go(func() error { + if stErr := d.stopContainer(d.scalaNode.container.Container.ID); stErr != nil { + log.Printf("Failed to stop Scala node container: %v", stErr) + } + return nil }) - if err != nil { - log.Printf("Failed to stop scala container: %v", err) - } } if d.goNode != nil { - err := d.pool.Client.KillContainer(dc.KillContainerOptions{ - ID: d.goNode.container.Container.ID, - Signal: dc.SIGINT, + eg.Go(func() error { + if stErr := d.stopContainer(d.goNode.container.Container.ID); stErr != nil { + log.Printf("Failed to stop Go node container: %v", stErr) + } + return nil }) - if err != nil { - log.Printf("Failed to stop go container: %v", err) - } } + _ = eg.Wait() if d.scalaNode != nil { - if err := d.pool.Purge(d.scalaNode.container); err != nil { - log.Printf("Failed to purge scala-node: %s", err) - } - if err := d.scalaNode.closeFiles(); err != nil { - log.Printf("Failed to close scala-node files: %s", err) - } + eg.Go(func() error { + if clErr := d.scalaNode.Close(); clErr != nil { + log.Printf("Failed to close scala-node: %s", clErr) + } + return nil + }) } if d.goNode != nil { - if err := d.pool.Purge(d.goNode.container); err != nil { - log.Printf("Failed to purge go-node: %s", err) - } - if err := d.goNode.closeFiles(); err != nil { - log.Printf("Failed to close go-node files: %s", err) - } + eg.Go(func() error { + if clErr := d.goNode.Close(); clErr != nil { + log.Printf("Failed to close go-node: %s", clErr) + } + return nil + }) } + _ = eg.Wait() if err := d.pool.RemoveNetwork(d.network); err != nil { log.Printf("Failed to remove docker network: %s", err) } cancel() } +func (d *Docker) stopContainer(containerID string) error { + const shutdownTimeout = 5 // In seconds. + if stErr := d.pool.Client.StopContainer(containerID, shutdownTimeout); stErr != nil { + if klErr := d.pool.Client.KillContainer(dc.KillContainerOptions{ + ID: containerID, + Signal: dc.SIGKILL, + }); klErr != nil { + return errors.Wrapf(stderrs.Join(stErr, klErr), "failed to stop container %q", containerID) + } + } + return nil +} + func (d *Docker) startNode( ctx context.Context, cfg config.DockerConfigurator, logFilename, errFilename string, ) (*NodeContainer, error) { @@ -182,7 +235,7 @@ func (d *Docker) startNode( opts.Networks = []*dockertest.Network{d.network} res, err := d.pool.RunWithOptions(opts, func(hc *dc.HostConfig) { - hc.AutoRemove = true + hc.AutoRemove = false hc.PublishAllPorts = true }) if err != nil { @@ -235,9 +288,11 @@ func (d *Docker) startNode( ApiKey: DefaultAPIKey, }) if fErr != nil { + log.Printf("Failed to create client for container %q: %v", res.Container.Name, fErr) return fErr } _, _, fErr = nodeClient.Blocks.Height(ctx) + log.Printf("Result requesting height from container %q: %v", res.Container.Name, fErr) return fErr }) if err != nil { @@ -247,9 +302,27 @@ func (d *Docker) startNode( } func (d *Docker) removeContainers() error { - err := d.pool.RemoveContainerByName(d.suite) + containers, err := d.pool.Client.ListContainers(dc.ListContainersOptions{ + All: true, + Filters: map[string][]string{ + "name": {d.suite}, + }, + }) if err != nil { - return errors.Wrapf(err, "failed to remove existing containers for suite %s", d.suite) + return fmt.Errorf("failed to list suite %q containers: %w", d.suite, err) + } + if len(containers) == 0 { + return nil + } + for _, c := range containers { + err = d.pool.Client.RemoveContainer(dc.RemoveContainerOptions{ + ID: c.ID, + Force: true, + RemoveVolumes: true, + }) + if err != nil { + return fmt.Errorf("failed to remove container %q of suite %q: %w", c.ID, d.suite, err) + } } return nil } @@ -269,7 +342,7 @@ func (d *Docker) removeNetworks() error { } func (d *Docker) createNetwork() error { - n, err := d.pool.CreateNetwork(d.suite + "-" + networkName) + n, err := d.pool.CreateNetwork(d.suite+"-"+networkName, WithIPv6Disabled) if err != nil { return errors.Wrapf(err, "failed to create network for suite %s", d.suite) } @@ -288,3 +361,7 @@ func (d *Docker) mkLogsDir() error { } return nil } + +func WithIPv6Disabled(conf *dc.CreateNetworkOptions) { + conf.EnableIPv6 = false +} diff --git a/itests/fixtures/base_fixtures.go b/itests/fixtures/base_fixtures.go index eb8f9a911..69170250d 100644 --- a/itests/fixtures/base_fixtures.go +++ b/itests/fixtures/base_fixtures.go @@ -34,19 +34,14 @@ func (suite *BaseSuite) BaseSetup(options ...config.BlockchainOption) { suite.Require().NoError(err, "couldn't create Go configurator") scalaConfigurator, err := config.NewScalaConfigurator(suiteName, cfg) suite.Require().NoError(err, "couldn't create Scala configurator") - + scalaConfigurator.WithGoNode("go-node") docker, err := d.NewDocker(suiteName) suite.Require().NoError(err, "couldn't create Docker pool") suite.Docker = docker - if gsErr := docker.StartGoNode(suite.MainCtx, goConfigurator); gsErr != nil { - docker.Finish(suite.Cancel) - suite.Require().NoError(gsErr, "couldn't start Go node container") - } - scalaConfigurator.WithGoNode(docker.GoNode().ContainerNetworkIP()) - if ssErr := docker.StartScalaNode(suite.MainCtx, scalaConfigurator); ssErr != nil { + if sErr := docker.StartNodes(suite.MainCtx, goConfigurator, scalaConfigurator); sErr != nil { docker.Finish(suite.Cancel) - suite.Require().NoError(ssErr, "couldn't start Scala node container") + suite.Require().NoError(sErr, "couldn't start nodes") } suite.Clients = clients.NewNodesClients(suite.MainCtx, suite.T(), docker.GoNode().Ports(), docker.ScalaNode().Ports()) @@ -54,7 +49,7 @@ func (suite *BaseSuite) BaseSetup(options ...config.BlockchainOption) { } func (suite *BaseSuite) SetupSuite() { - suite.BaseSetup(config.WithScalaMining()) + suite.BaseSetup() } func (suite *BaseSuite) TearDownSuite() { diff --git a/itests/fixtures/lite_node_feature_fixture.go b/itests/fixtures/lite_node_feature_fixture.go deleted file mode 100644 index 2ca7f9ec2..000000000 --- a/itests/fixtures/lite_node_feature_fixture.go +++ /dev/null @@ -1,19 +0,0 @@ -package fixtures - -import "github.com/wavesplatform/gowaves/itests/config" - -const ( - baseSettingsConfigFolder = "base_feature_settings" -) - -type BaseSettingSuite struct { - BaseSuite -} - -func (suite *BaseSettingSuite) SetupSuite() { - suite.BaseSetup( - config.WithScalaMining(), - config.WithFeatureSettingFromFile(baseSettingsConfigFolder, "lite_node_feature_fixture.json"), - config.WithPaymentsSettingFromFile(baseSettingsConfigFolder, "lite_node_feature_fixture.json"), - ) -} diff --git a/itests/fixtures/reward_preactivated_features_fixtures.go b/itests/fixtures/reward_preactivated_features_fixtures.go index d2108ffcb..56c8f26ce 100644 --- a/itests/fixtures/reward_preactivated_features_fixtures.go +++ b/itests/fixtures/reward_preactivated_features_fixtures.go @@ -21,7 +21,6 @@ type RewardIncreaseDaoXtnPreactivatedSuite struct { func (suite *RewardIncreaseDaoXtnPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -44,7 +43,6 @@ type RewardUnchangedDaoXtnPreactivatedSuite struct { func (suite *RewardUnchangedDaoXtnPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -67,7 +65,6 @@ type RewardDecreaseDaoXtnPreactivatedSuite struct { func (suite *RewardDecreaseDaoXtnPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -90,7 +87,6 @@ type RewardIncreaseDaoPreactivatedSuite struct { func (suite *RewardIncreaseDaoPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -113,7 +109,6 @@ type RewardUnchangedXtnPreactivatedSuite struct { func (suite *RewardUnchangedXtnPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -136,7 +131,6 @@ type Reward2WUnchangedDaoXtnPreactivatedSuite struct { func (suite *Reward2WUnchangedDaoXtnPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -159,7 +153,6 @@ type RewardDecreaseDaoPreactivatedSuite struct { func (suite *RewardDecreaseDaoPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -182,7 +175,6 @@ type RewardDecreaseXtnPreactivatedSuite struct { func (suite *RewardDecreaseXtnPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -205,7 +197,6 @@ type RewardIncreasePreactivatedSuite struct { func (suite *RewardIncreasePreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -228,7 +219,6 @@ type RewardDaoXtnPreactivatedWithout19Suite struct { func (suite *RewardDaoXtnPreactivatedWithout19Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -251,7 +241,6 @@ type RewardDaoXtnPreactivatedWithout20Suite struct { func (suite *RewardDaoXtnPreactivatedWithout20Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesConfigFolder, @@ -276,7 +265,6 @@ type RewardIncreaseDaoXtnCeaseXTNBuybackPreactivatedSuite struct { func (suite *RewardIncreaseDaoXtnCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -299,7 +287,6 @@ type RewardIncreaseXtnCeaseXTNBuybackPreactivatedSuite struct { func (suite *RewardIncreaseXtnCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -322,7 +309,6 @@ type RewardUnchangedDaoXtnCeaseXTNBuybackPreactivatedSuite struct { func (suite *RewardUnchangedDaoXtnCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -345,7 +331,6 @@ type RewardDecreaseDaoXtnCeaseXTNBuybackPreactivatedSuite struct { func (suite *RewardDecreaseDaoXtnCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -368,7 +353,6 @@ type RewardDecreaseXtnCeaseXTNBuybackPreactivatedSuite struct { func (suite *RewardDecreaseXtnCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -391,7 +375,6 @@ type Reward2WUnchangedDaoXtnCeaseXTNBuybackPreactivatedSuite struct { func (suite *Reward2WUnchangedDaoXtnCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -414,7 +397,6 @@ type Reward5W2MinersIncreaseCeaseXTNBuybackPreactivatedSuite struct { func (suite *Reward5W2MinersIncreaseCeaseXTNBuybackPreactivatedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -437,7 +419,6 @@ type RewardDaoXtnPreactivatedWith21Suite struct { func (suite *RewardDaoXtnPreactivatedWith21Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, @@ -460,7 +441,6 @@ type RewardDaoXtnPreactivatedWithout19And20Suite struct { func (suite *RewardDaoXtnPreactivatedWithout19And20Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, preactivatedFeaturesWith21ConfigFolder, diff --git a/itests/fixtures/reward_supported_features_fixtures.go b/itests/fixtures/reward_supported_features_fixtures.go index c72508f29..97a7a63d1 100644 --- a/itests/fixtures/reward_supported_features_fixtures.go +++ b/itests/fixtures/reward_supported_features_fixtures.go @@ -21,7 +21,6 @@ type RewardIncreaseDaoXtnSupportedSuite struct { func (suite *RewardIncreaseDaoXtnSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -44,7 +43,6 @@ type RewardUnchangedDaoXtnSupportedSuite struct { func (suite *RewardUnchangedDaoXtnSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -67,7 +65,6 @@ type RewardDecreaseDaoXtnSupportedSuite struct { func (suite *RewardDecreaseDaoXtnSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -90,7 +87,6 @@ type RewardIncreaseDaoSupportedSuite struct { func (suite *RewardIncreaseDaoSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -113,7 +109,6 @@ type RewardUnchangedXtnSupportedSuite struct { func (suite *RewardUnchangedXtnSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -136,7 +131,6 @@ type Reward2WUnchangedDaoXtnSupportedSuite struct { func (suite *Reward2WUnchangedDaoXtnSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -159,7 +153,6 @@ type RewardDecreaseDaoSupportedSuite struct { func (suite *RewardDecreaseDaoSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -182,7 +175,6 @@ type RewardDecreaseXtnSupportedSuite struct { func (suite *RewardDecreaseXtnSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -205,7 +197,6 @@ type RewardIncreaseSupportedSuite struct { func (suite *RewardIncreaseSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -228,7 +219,6 @@ type RewardDaoXtnSupportedWithout19Suite struct { func (suite *RewardDaoXtnSupportedWithout19Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -253,7 +243,6 @@ type RewardIncreaseDaoXtnCeaseXTNBuybackSupportedSuite struct { func (suite *RewardIncreaseDaoXtnCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -276,7 +265,6 @@ type RewardIncreaseXtnCeaseXTNBuybackSupportedSuite struct { func (suite *RewardIncreaseXtnCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -299,7 +287,6 @@ type RewardUnchangedDaoXtnCeaseXTNBuybackSupportedSuite struct { func (suite *RewardUnchangedDaoXtnCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -322,7 +309,6 @@ type RewardDecreaseDaoXtnCeaseXTNBuybackSupportedSuite struct { func (suite *RewardDecreaseDaoXtnCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -345,7 +331,6 @@ type RewardDecreaseXtnCeaseXTNBuybackSupportedSuite struct { func (suite *RewardDecreaseXtnCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -368,7 +353,6 @@ type Reward2WUnchangedDaoXtnCeaseXTNBuybackSupportedSuite struct { func (suite *Reward2WUnchangedDaoXtnCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -391,7 +375,6 @@ type Reward5W2MinersIncreaseCeaseXTNBuybackSupportedSuite struct { func (suite *Reward5W2MinersIncreaseCeaseXTNBuybackSupportedSuite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -414,7 +397,6 @@ type RewardDaoXtnSupportedWithout20Suite struct { func (suite *RewardDaoXtnSupportedWithout20Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -437,7 +419,6 @@ type RewardDaoXtnSupportedWithout19And20Suite struct { func (suite *RewardDaoXtnSupportedWithout19And20Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, @@ -462,7 +443,6 @@ type RewardIncreaseDaoXtnSupported20Suite struct { func (suite *RewardIncreaseDaoXtnSupported20Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeature20ConfigFolder, @@ -485,7 +465,6 @@ type RewardDaoXtnSupported19Suite struct { func (suite *RewardDaoXtnSupported19Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesConfigFolder, @@ -508,7 +487,6 @@ type RewardDistributionRollbackBefore21Suite struct { func (suite *RewardDistributionRollbackBefore21Suite) SetupSuite() { suite.BaseSetup( - config.WithScalaMining(), config.WithFeatureSettingFromFile( rewardSettingsFolder, supportedFeaturesWith21ConfigFolder, diff --git a/itests/fixtures/single_go_node_suite.go b/itests/fixtures/single_go_node_suite.go new file mode 100644 index 000000000..599175835 --- /dev/null +++ b/itests/fixtures/single_go_node_suite.go @@ -0,0 +1,69 @@ +package fixtures + +import ( + "context" + + "github.com/stoewer/go-strcase" + "github.com/stretchr/testify/suite" + + "github.com/wavesplatform/gowaves/itests/clients" + "github.com/wavesplatform/gowaves/itests/config" + d "github.com/wavesplatform/gowaves/itests/docker" + "github.com/wavesplatform/gowaves/pkg/proto" +) + +type SingleGoNodeSuite struct { + suite.Suite + + MainCtx context.Context + Cancel context.CancelFunc + Cfg config.TestConfig + Docker *d.Docker + Client *clients.NodeUniversalClient +} + +func (suite *SingleGoNodeSuite) BaseSetup(options ...config.BlockchainOption) { + suite.MainCtx, suite.Cancel = context.WithCancel(context.Background()) + suiteName := strcase.KebabCase(suite.T().Name()) + cfg, err := config.NewBlockchainConfig(options...) + suite.Require().NoError(err, "couldn't create blockchain config") + suite.Cfg = cfg.TestConfig() + + goConfigurator, err := config.NewGoConfigurator(suiteName, cfg) + suite.Require().NoError(err, "couldn't create Go configurator") + + docker, err := d.NewDocker(suiteName) + suite.Require().NoError(err, "couldn't create Docker pool") + suite.Docker = docker + + if sErr := docker.StartGoNode(suite.MainCtx, goConfigurator); sErr != nil { + docker.Finish(suite.Cancel) + suite.Require().NoError(sErr, "couldn't start Go node container") + } + + gp, err := proto.NewPeerInfoFromString(config.DefaultIP + ":" + docker.GoNode().Ports().BindPort) + suite.Require().NoError(err, "failed to create Go peer info") + peers := []proto.PeerInfo{gp} + suite.Client = clients.NewNodeUniversalClient(suite.MainCtx, suite.T(), clients.NodeGo, + docker.GoNode().Ports().RESTAPIPort, docker.GoNode().Ports().GRPCPort, docker.GoNode().Ports().BindPort, + peers, + ) + suite.Client.Handshake() +} + +func (suite *SingleGoNodeSuite) SetupSuite() { + suite.BaseSetup() +} + +func (suite *SingleGoNodeSuite) TearDownSuite() { + suite.Client.Close(suite.T()) + suite.Docker.Finish(suite.Cancel) +} + +func (suite *SingleGoNodeSuite) SetupTest() { + suite.Client.SendStartMessage(suite.T()) +} + +func (suite *SingleGoNodeSuite) TearDownTest() { + suite.Client.SendEndMessage(suite.T()) +} diff --git a/itests/init_internal_test.go b/itests/init_internal_test.go index 4d9f50b94..8f10825d7 100644 --- a/itests/init_internal_test.go +++ b/itests/init_internal_test.go @@ -36,7 +36,12 @@ func TestMain(m *testing.M) { if err != nil { log.Fatalf("Failed to create docker pool: %v", err) } - if err := pool.Client.PullImage(dc.PullImageOptions{Repository: "wavesplatform/wavesnode", Tag: "latest"}, dc.AuthConfiguration{}); err != nil { + if plErr := pool.Client.PullImage( + dc.PullImageOptions{ + Repository: "wavesplatform/wavesnode", + Tag: "latest", + Platform: "linux/amd64"}, + dc.AuthConfiguration{}); plErr != nil { log.Fatalf("Failed to pull node image: %v", err) } var buildArgs []dc.BuildArg diff --git a/itests/snapshot_internal_test.go b/itests/snapshot_internal_test.go new file mode 100644 index 000000000..6e2c1213d --- /dev/null +++ b/itests/snapshot_internal_test.go @@ -0,0 +1,262 @@ +package itests + +import ( + "encoding/binary" + "math" + "math/big" + "reflect" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/wavesplatform/gowaves/itests/config" + "github.com/wavesplatform/gowaves/itests/fixtures" + "github.com/wavesplatform/gowaves/pkg/consensus" + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/settings" +) + +type SimpleSnapshotSuite struct { + fixtures.SingleGoNodeSuite +} + +func (s *SimpleSnapshotSuite) SetupSuite() { + s.BaseSetup( + config.WithNoGoMining(), + config.WithPreactivatedFeatures([]config.FeatureInfo{{Feature: int16(settings.LightNode), Height: 1}}), + config.WithAbsencePeriod(1), + ) +} + +func (s *SimpleSnapshotSuite) TestSimpleSnapshot() { + const messageTimeout = 5 * time.Second + + acc := s.Cfg.GetRichestAccount() + + // Initialize genesis block ID. + err := s.Cfg.BlockchainSettings.Genesis.GenerateBlockID(s.Cfg.BlockchainSettings.AddressSchemeCharacter) + require.NoError(s.T(), err, "failed to generate genesis block ID") + genesisID := s.Cfg.BlockchainSettings.Genesis.BlockID() + + // Calculate state hash for the key-block. Take into account only miner's reward. + genesisSH := s.Cfg.GenesisSH() + + // Calculate new balance of the richest account (block generator). + newBalance := acc.Amount + s.Cfg.BlockchainSettings.InitialBlockReward + + // Calculate state hash for the key-block. + sh, err := keyBlockSH(genesisSH, acc.Address, newBalance) + require.NoError(s.T(), err, "failed to calculate state hash") + + // Generate key-block + bl, delay := createKeyBlock(s.T(), s.Cfg.BlockchainSettings.Genesis.GenSignature, s.Cfg.BlockchainSettings, + acc.SecretKey, acc.PublicKey, newBalance, genesisID, s.Cfg.BlockchainSettings.Genesis.Timestamp, + s.Cfg.BlockchainSettings.Genesis.BaseTarget, sh) + if delay > 0 { + time.Sleep(delay) + } + + err = s.Client.Connection.SubscribeForMessages( + reflect.TypeOf(&proto.GetBlockIdsMessage{}), + reflect.TypeOf(&proto.GetBlockMessage{}), + reflect.TypeOf(&proto.ScoreMessage{}), + reflect.TypeOf(&proto.MicroBlockRequestMessage{}), + ) + require.NoError(s.T(), err, "failed to subscribe for messages") + + // Calculate new score and send score to the node. + genesisScore := calculateScore(s.Cfg.BlockchainSettings.Genesis.BaseTarget) + blockScore := calculateCumulativeScore(genesisScore, bl.BaseTarget) + scoreMsg := &proto.ScoreMessage{Score: blockScore.Bytes()} + s.Client.Connection.SendMessage(scoreMsg) + + // Wait for the node to request block IDs. + _, err = s.Client.Connection.AwaitMessage(reflect.TypeOf(&proto.GetBlockIdsMessage{}), messageTimeout) + require.NoError(s.T(), err, "failed to wait for block IDs request") + + // Send block IDs to the node. + blocksMsg := &proto.BlockIdsMessage{Blocks: []proto.BlockID{bl.BlockID()}} + s.Client.Connection.SendMessage(blocksMsg) + + // Wait for the node to request the block. + blockID, err := s.Client.Connection.AwaitGetBlockMessage(messageTimeout) + require.NoError(s.T(), err, "failed to wait for block request") + assert.Equal(s.T(), bl.BlockID(), blockID) + + // Marshal the block and send it to the node. + bb, err := bl.MarshalToProtobuf(s.Cfg.BlockchainSettings.AddressSchemeCharacter) + require.NoError(s.T(), err, "failed to marshal block") + blMsg := &proto.PBBlockMessage{PBBlockBytes: bb} + s.Client.Connection.SendMessage(blMsg) + + // Wait for updated score message. + score, err := s.Client.Connection.AwaitScoreMessage(messageTimeout) + require.NoError(s.T(), err, "failed to wait for score") + assert.Equal(s.T(), blockScore, score) + + // Wait for 2.5 seconds and send micro-block (imitate real life). + time.Sleep(2500 * time.Millisecond) + + // Add transactions to block. + tx := proto.NewUnsignedTransferWithProofs(3, acc.PublicKey, + proto.NewOptionalAssetWaves(), proto.NewOptionalAssetWaves(), uint64(time.Now().UnixMilli()), 1_0000_0000, + 100_000, proto.NewRecipientFromAddress(acc.Address), nil) + err = tx.Sign(s.Cfg.BlockchainSettings.AddressSchemeCharacter, acc.SecretKey) + require.NoError(s.T(), err, "failed to sign tx") + + // Create micro-block with the transaction and unchanged state hash. + mb, inv := createMicroBlockAndInv(s.T(), *bl, s.Cfg.BlockchainSettings, tx, acc.SecretKey, acc.PublicKey, sh) + + // Send micro-block inv to the node. + ib, err := inv.MarshalBinary() + require.NoError(s.T(), err, "failed to marshal inv") + invMsg := &proto.MicroBlockInvMessage{Body: ib} + s.Client.Connection.SendMessage(invMsg) + + // Wait for the node to request micro-block. + mbID, err := s.Client.Connection.AwaitMicroblockRequest(messageTimeout) + require.NoError(s.T(), err, "failed to wait for micro-block request") + assert.Equal(s.T(), inv.TotalBlockID, mbID) + + // Marshal the micro-block and send it to the node. + mbb, err := mb.MarshalToProtobuf(s.Cfg.BlockchainSettings.AddressSchemeCharacter) + require.NoError(s.T(), err, "failed to marshal micro block") + mbMsg := &proto.PBMicroBlockMessage{MicroBlockBytes: mbb} + s.Client.Connection.SendMessage(mbMsg) + + h := s.Client.HTTPClient.GetHeight(s.T()) + header := s.Client.HTTPClient.BlockHeader(s.T(), h.Height) + assert.Equal(s.T(), bl.BlockID().String(), header.ID.String()) +} + +func TestSimpleSnapshotSuite(t *testing.T) { + t.Parallel() + suite.Run(t, new(SimpleSnapshotSuite)) +} + +func keyBlockSH(prevSH crypto.Digest, miner proto.WavesAddress, balance uint64) (crypto.Digest, error) { + hash, err := crypto.NewFastHash() + if err != nil { + return crypto.Digest{}, errors.Wrap(err, "failed to calculate key block snapshot hash") + } + + buf := make([]byte, proto.WavesAddressSize+8) + copy(buf, miner[:]) + binary.BigEndian.PutUint64(buf[proto.WavesAddressSize:], balance) + hash.Write(buf) + + var txSHD crypto.Digest + hash.Sum(txSHD[:0]) + + hash.Reset() + hash.Write(prevSH.Bytes()) + hash.Write(txSHD.Bytes()) + + var r crypto.Digest + hash.Sum(r[:0]) + return r, nil +} + +func createKeyBlock(t *testing.T, hitSource []byte, cfg *settings.BlockchainSettings, + generatorSK crypto.SecretKey, generatorPK crypto.PublicKey, generatorBalance uint64, + parentID proto.BlockID, parentTimestamp uint64, parentBaseTarget uint64, + sh crypto.Digest, +) (*proto.Block, time.Duration) { + gsp := consensus.VRFGenerationSignatureProvider + pos := consensus.NewFairPosCalculator(cfg.DelayDelta, cfg.MinBlockTime) + gs, err := gsp.GenerationSignature(generatorSK, hitSource) + require.NoError(t, err, "failed to generate generation signature") + + source, err := gsp.HitSource(generatorSK, hitSource) + require.NoError(t, err, "failed to generate hit source") + + hit, err := consensus.GenHit(source) + require.NoError(t, err, "failed to generate hit from source") + + delay, err := pos.CalculateDelay(hit, parentBaseTarget, generatorBalance) + require.NoError(t, err, "failed to calculate delay") + + ts := parentTimestamp + delay + bt, err := pos.CalculateBaseTarget(cfg.AverageBlockDelaySeconds, 1, parentBaseTarget, parentTimestamp, 0, ts) + require.NoError(t, err, "failed to calculate base target") + + nxt := proto.NxtConsensus{BaseTarget: bt, GenSignature: gs} + + bl, err := proto.CreateBlock(proto.Transactions(nil), ts, parentID, generatorPK, nxt, proto.ProtobufBlockVersion, + nil, int64(cfg.InitialBlockReward), cfg.AddressSchemeCharacter, &sh) + require.NoError(t, err, "failed to create block") + + // Sign the block and generate its ID. + err = bl.Sign(cfg.AddressSchemeCharacter, generatorSK) + require.NoError(t, err, "failed to sing the block") + + err = bl.GenerateBlockID(cfg.AddressSchemeCharacter) + require.NoError(t, err, "failed to generate block ID") + + return bl, time.Until(time.UnixMilli(int64(ts))) +} + +func createMicroBlockAndInv(t *testing.T, b proto.Block, cfg *settings.BlockchainSettings, tx proto.Transaction, + generatorSK crypto.SecretKey, generatorPK crypto.PublicKey, sh crypto.Digest, +) (*proto.MicroBlock, *proto.MicroBlockInv) { + b.Transactions = []proto.Transaction{tx} + b.TransactionCount = len(b.Transactions) + err := b.SetTransactionsRootIfPossible(cfg.AddressSchemeCharacter) + require.NoError(t, err, "failed to set transactions root") + err = b.Sign(cfg.AddressSchemeCharacter, generatorSK) + require.NoError(t, err, "failed to sign block") + err = b.GenerateBlockID(cfg.AddressSchemeCharacter) + require.NoError(t, err, "failed to generate block ID") + + mb := &proto.MicroBlock{ + VersionField: byte(b.Version), + SenderPK: generatorPK, + Transactions: b.Transactions, + TransactionCount: uint32(b.TransactionCount), + Reference: b.ID, + TotalResBlockSigField: b.BlockSignature, + TotalBlockID: b.BlockID(), + StateHash: &sh, + } + + err = mb.Sign(cfg.AddressSchemeCharacter, generatorSK) + require.NoError(t, err, "failed to sign mb block") + + inv := proto.NewUnsignedMicroblockInv(generatorPK, mb.TotalBlockID, mb.Reference) + err = inv.Sign(generatorSK, cfg.AddressSchemeCharacter) + require.NoError(t, err, "failed to sign inv") + + return mb, inv +} + +func calculateScore(baseTarget uint64) *big.Int { + const decimalBase = 10 + + res := big.NewInt(0) + if baseTarget == 0 { + return res + } + if baseTarget > math.MaxInt64 { + panic("base target is too big") + } + bt := big.NewInt(int64(baseTarget)) + maxBlockScore, ok := big.NewInt(0).SetString("18446744073709551616", decimalBase) + if !ok { + return res + } + res.Div(maxBlockScore, bt) + return res +} + +func calculateCumulativeScore(parentScore *big.Int, baseTarget uint64) *big.Int { + s := calculateScore(baseTarget) + if parentScore == nil { + return s + } + return s.Add(s, parentScore) +} diff --git a/itests/testdata/feature_settings/base_feature_settings/lite_node_feature_fixture.json b/itests/testdata/feature_settings/base_feature_settings/lite_node_feature_fixture.json deleted file mode 100644 index 6948940a1..000000000 --- a/itests/testdata/feature_settings/base_feature_settings/lite_node_feature_fixture.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "payments_fix_after_height": 1, - "preactivated_features": [ - { - "feature": 19, - "height": 1 - }, - { - "feature": 20, - "height": 1 - }, - { - "feature": 21, - "height": 1 - }, - { - "feature": 22, - "height": 1 - } - ], - "supported_features": [] -} \ No newline at end of file diff --git a/itests/utilities/common.go b/itests/utilities/common.go index 960823072..0d6f52924 100644 --- a/itests/utilities/common.go +++ b/itests/utilities/common.go @@ -394,8 +394,16 @@ func GetHeightScala(suite *f.BaseSuite) uint64 { } func GetHeight(suite *f.BaseSuite) uint64 { - goHeight := GetHeightGo(suite) - scalaHeight := GetHeightScala(suite) + goCh := make(chan uint64) + scalaCh := make(chan uint64) + go func() { + goCh <- GetHeightGo(suite) + }() + go func() { + scalaCh <- GetHeightScala(suite) + }() + goHeight := <-goCh + scalaHeight := <-scalaCh if goHeight < scalaHeight { return goHeight } @@ -481,30 +489,41 @@ func GetFeatureActivationHeightScala(suite *f.BaseSuite, featureID settings.Feat } func GetFeatureActivationHeight(suite *f.BaseSuite, featureID settings.Feature, height uint64) proto.Height { - var err error - var activationHeight proto.Height - activationHeightGo := GetFeatureActivationHeightGo(suite, featureID, height) - activationHeightScala := GetFeatureActivationHeightScala(suite, featureID, height) + goCh := make(chan proto.Height) + scalaCh := make(chan proto.Height) + go func() { + goCh <- GetFeatureActivationHeightGo(suite, featureID, height) + }() + go func() { + scalaCh <- GetFeatureActivationHeightScala(suite, featureID, height) + }() + activationHeightGo := <-goCh + activationHeightScala := <-scalaCh + if activationHeightGo == activationHeightScala && activationHeightGo > 0 { - activationHeight = activationHeightGo - } else { - err = errors.New("Activation Height from Go and Scala is different") + return activationHeightGo } - require.NoError(suite.T(), err) - return activationHeight + + suite.FailNow("Activation Height from Go and Scala is different") + return 0 } func GetFeatureBlockchainStatus(suite *f.BaseSuite, featureID settings.Feature, height uint64) (string, error) { - var status string - var err error - statusGo := GetFeatureBlockchainStatusGo(suite, featureID, height) - statusScala := GetFeatureBlockchainStatusScala(suite, featureID, height) + goCh := make(chan string) + scalaCh := make(chan string) + go func() { + goCh <- GetFeatureBlockchainStatusGo(suite, featureID, height) + }() + go func() { + scalaCh <- GetFeatureBlockchainStatusScala(suite, featureID, height) + }() + statusGo := <-goCh + statusScala := <-scalaCh + if statusGo == statusScala { - status = statusGo - } else { - err = errors.Errorf("Feature with Id %d has different statuses", featureID) + return statusGo, nil } - return status, err + return "", errors.Errorf("Feature with ID %d has different statuses", featureID) } func GetWaitingBlocks(suite *f.BaseSuite, height uint64, featureID settings.Feature) uint64 { @@ -531,17 +550,26 @@ func GetWaitingBlocks(suite *f.BaseSuite, height uint64, featureID settings.Feat } func WaitForFeatureActivation(suite *f.BaseSuite, featureID settings.Feature, height uint64) proto.Height { - var activationHeight proto.Height waitingBlocks := GetWaitingBlocks(suite, height, featureID) h := WaitForHeight(suite, height+waitingBlocks) - activationHeightGo := GetFeatureActivationHeightGo(suite, featureID, h) - activationHeightScala := GetFeatureActivationHeightScala(suite, featureID, h) + + goCh := make(chan proto.Height) + scalaCh := make(chan proto.Height) + + go func() { + goCh <- GetFeatureActivationHeightGo(suite, featureID, h) + }() + go func() { + scalaCh <- GetFeatureActivationHeightScala(suite, featureID, h) + }() + activationHeightGo := <-goCh + activationHeightScala := <-scalaCh + if activationHeightScala == activationHeightGo { - activationHeight = activationHeightGo - } else { - suite.FailNowf("Feature has different activation heights", "Feature ID is %d", featureID) + return activationHeightGo } - return activationHeight + suite.FailNowf("Feature has different activation heights", "Feature ID is %d", featureID) + return 0 } func FeatureShouldBeActivated(suite *f.BaseSuite, featureID settings.Feature, height uint64) { @@ -884,8 +912,16 @@ func GetRewardTermAtHeightScala(suite *f.BaseSuite, height uint64) uint64 { } func GetRewardTermAtHeight(suite *f.BaseSuite, height uint64) RewardTerm { - termGo := GetRewardTermAtHeightGo(suite, height) - termScala := GetRewardTermAtHeightScala(suite, height) + goCh := make(chan uint64) + scalaCh := make(chan uint64) + go func() { + goCh <- GetRewardTermAtHeightGo(suite, height) + }() + go func() { + scalaCh <- GetRewardTermAtHeightScala(suite, height) + }() + termGo := <-goCh + termScala := <-scalaCh suite.T().Logf("Go: Reward Term: %d, Scala: Reward Term: %d, height: %d", termGo, termScala, height) return NewRewardTerm(termGo, termScala) @@ -917,5 +953,13 @@ func GetRollbackToHeightScala(suite *f.BaseSuite, height uint64, returnTxToUtx b func GetRollbackToHeight(suite *f.BaseSuite, height uint64, returnTxToUtx bool) (*proto.BlockID, *proto.BlockID) { suite.T().Logf("Rollback to height: %d from height: %d", height, GetHeight(suite)) - return GetRollbackToHeightGo(suite, height, returnTxToUtx), GetRollbackToHeightScala(suite, height, returnTxToUtx) + goCh := make(chan *proto.BlockID) + scalaCh := make(chan *proto.BlockID) + go func() { + goCh <- GetRollbackToHeightGo(suite, height, returnTxToUtx) + }() + go func() { + scalaCh <- GetRollbackToHeightScala(suite, height, returnTxToUtx) + }() + return <-goCh, <-scalaCh }