github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/e2e/connection_test.go (about)

     1  /*
     2   * Copyright (C) 2018 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package e2e
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"math"
    24  	"math/big"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/ethereum/go-ethereum/accounts"
    32  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    33  	"github.com/ethereum/go-ethereum/accounts/keystore"
    34  	"github.com/ethereum/go-ethereum/common"
    35  	"github.com/ethereum/go-ethereum/core/types"
    36  	"github.com/ethereum/go-ethereum/ethclient"
    37  	"github.com/rs/zerolog/log"
    38  	"github.com/stretchr/testify/assert"
    39  
    40  	"github.com/mysteriumnetwork/node/identity"
    41  	"github.com/mysteriumnetwork/node/requests"
    42  	"github.com/mysteriumnetwork/node/session/pingpong"
    43  	tequilapi_client "github.com/mysteriumnetwork/node/tequilapi/client"
    44  	"github.com/mysteriumnetwork/node/tequilapi/contract"
    45  	"github.com/mysteriumnetwork/payments/bindings"
    46  	"github.com/mysteriumnetwork/payments/crypto"
    47  )
    48  
    49  var (
    50  	consumerPassphrase          = "localconsumer"
    51  	providerID                  = "0xd1a23227bd5ad77f36ba62badcb78a410a1db6c5"
    52  	providerPassphrase          = "localprovider"
    53  	chainID               int64 = 80001
    54  	hermesID                    = "0xd68defb97d0765741f8ecf179df2f9564e1466a3"
    55  	hermes2ID                   = "0xfd63dc49c7163d82d6f0a4c23ff13216d702ce50"
    56  	mystAddress                 = "0xaa9c4e723609cb913430143fbc86d3cbe7adca21"
    57  	registryAddress             = "0x427c2bad22335710aec5e477f3e3adcd313a9bcb"
    58  	channelImplementation       = "0x599d43715df3070f83355d9d90ae62c159e62a75"
    59  	addressForTopups            = "0xa29fb77b25181df094908b027821a7492ca4245b"
    60  	tenthThou                   = float64(1) / float64(10000)
    61  )
    62  
    63  var (
    64  	ethClient        *ethclient.Client
    65  	ethClientL2      *ethclient.Client
    66  	ethSignerBuilder func(client *ethclient.Client) func(address common.Address, tx *types.Transaction) (*types.Transaction, error)
    67  )
    68  
    69  var (
    70  	providerChannelAddress      = "0xa2305c7214045100B6EF5Df8f8FEc5C57F42051A"
    71  	balanceAfterRegistration, _ = big.NewInt(0).SetString("7000000000000000000", 10)
    72  	registrationFee, _          = big.NewInt(0).SetString("100000000000000000", 10)
    73  )
    74  
    75  type consumer struct {
    76  	consumerID     string
    77  	serviceType    string
    78  	tequila        func() *tequilapi_client.Client
    79  	balanceSpent   *big.Int
    80  	proposal       contract.ProposalDTO
    81  	composeService string
    82  	hermesID       common.Address
    83  	hermesURL      string
    84  }
    85  
    86  var consumersToTest = []*consumer{
    87  	{
    88  		serviceType:    "noop",
    89  		hermesID:       common.HexToAddress(hermesID),
    90  		composeService: "myst-consumer-noop",
    91  		hermesURL:      "http://hermes:8889/api/v2",
    92  		tequila: func() *tequilapi_client.Client {
    93  			return newTequilapiConsumer("myst-consumer-noop")
    94  		},
    95  	},
    96  	{
    97  		hermesID:       common.HexToAddress(hermesID),
    98  		serviceType:    "wireguard",
    99  		hermesURL:      "http://hermes:8889/api/v2",
   100  		composeService: "myst-consumer-wireguard",
   101  		tequila: func() *tequilapi_client.Client {
   102  			return newTequilapiConsumer("myst-consumer-wireguard")
   103  		},
   104  	},
   105  	{
   106  		hermesID:       common.HexToAddress(hermesID),
   107  		hermesURL:      "http://hermes:8889/api/v2",
   108  		serviceType:    "openvpn",
   109  		composeService: "myst-consumer-openvpn",
   110  		tequila: func() *tequilapi_client.Client {
   111  			return newTequilapiConsumer("myst-consumer-openvpn")
   112  		},
   113  	},
   114  	{
   115  		hermesURL:      "http://hermes2:8889/api/v2",
   116  		hermesID:       common.HexToAddress(hermes2ID),
   117  		serviceType:    "wireguard",
   118  		composeService: "myst-consumer-hermes2",
   119  		tequila: func() *tequilapi_client.Client {
   120  			return newTequilapiConsumer("myst-consumer-hermes2")
   121  		},
   122  	},
   123  }
   124  
   125  func TestConsumerConnectsToProvider(t *testing.T) {
   126  	initEthClient(t)
   127  
   128  	tequilapiProvider := newTequilapiProvider()
   129  	t.Run("Provider has a registered identity", func(t *testing.T) {
   130  		providerRegistrationFlow(t, tequilapiProvider, providerID, providerPassphrase)
   131  	})
   132  
   133  	t.Run("Consumer Creates And Registers Identity", func(t *testing.T) {
   134  		wg := sync.WaitGroup{}
   135  		wg.Add(len(consumersToTest))
   136  		topUps := make(chan *consumer, len(consumersToTest))
   137  		defer close(topUps)
   138  
   139  		go func() {
   140  			for c := range topUps {
   141  				fees, err := c.tequila().GetTransactorFees()
   142  				assert.NoError(t, err)
   143  				topUpConsumer(t, c.consumerID, c.hermesID, fees.Registration)
   144  			}
   145  		}()
   146  
   147  		for _, c := range consumersToTest {
   148  			go func(c *consumer) {
   149  				defer wg.Done()
   150  				tequilapiConsumer := c.tequila()
   151  				consumerID := identityCreateFlow(t, tequilapiConsumer, consumerPassphrase)
   152  				c.consumerID = consumerID
   153  				topUps <- c
   154  				consumerRegistrationFlow(t, tequilapiConsumer, consumerID, consumerPassphrase)
   155  			}(c)
   156  		}
   157  
   158  		wg.Wait()
   159  	})
   160  
   161  	t.Run("Consumers Connect to provider", func(t *testing.T) {
   162  		wg := sync.WaitGroup{}
   163  		for _, c := range consumersToTest {
   164  			wg.Add(1)
   165  			go func(c *consumer) {
   166  				defer wg.Done()
   167  				proposal := consumerPicksProposal(t, c.tequila(), c.serviceType)
   168  				balanceSpent := consumerConnectFlow(t, c.tequila(), c.consumerID, c.hermesID.Hex(), c.serviceType, proposal)
   169  				c.balanceSpent = balanceSpent
   170  				c.proposal = proposal
   171  				recheckBalancesWithHermes(t, c.consumerID, balanceSpent, c.serviceType, c.hermesURL)
   172  			}(c)
   173  		}
   174  
   175  		wg.Wait()
   176  	})
   177  	t.Run("Validate provider earnings", func(t *testing.T) {
   178  		sum := new(big.Int)
   179  		for _, v := range consumersToTest {
   180  			sum = new(big.Int).Add(sum, v.balanceSpent)
   181  		}
   182  		providerEarnedTokens(t, tequilapiProvider, providerID, sum)
   183  	})
   184  
   185  	t.Run("Provider settlement flow", func(t *testing.T) {
   186  		caller, err := bindings.NewMystTokenCaller(common.HexToAddress(mystAddress), ethClient)
   187  		assert.NoError(t, err)
   188  
   189  		providerStatus, err := tequilapiProvider.Identity(providerID)
   190  		assert.NoError(t, err)
   191  		assert.Equal(t, balanceAfterRegistration, providerStatus.Balance)
   192  
   193  		// try to settle hermes 1
   194  		hermeses := []common.Address{
   195  			common.HexToAddress(hermesID),
   196  		}
   197  		err = tequilapiProvider.Settle(identity.FromAddress(providerID), hermeses, false)
   198  		assert.Error(t, err)
   199  		assert.Contains(t, err.Error(), "is set as beneficiary, skip settling")
   200  
   201  		hermeses = append(hermeses, common.HexToAddress(hermes2ID))
   202  		beneficiary := "0x1234aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa123412"
   203  		err = tequilapiProvider.SetBeneficiaryAsync(providerID, beneficiary)
   204  		assert.NoError(t, err)
   205  
   206  		// settle hermes 1 and 2 (should settle with beneficiary)
   207  		err = tequilapiProvider.Settle(identity.FromAddress(providerID), hermeses, true)
   208  		assert.NoError(t, err)
   209  
   210  		providerStatus, err = tequilapiProvider.Identity(providerID)
   211  		assert.NoError(t, err)
   212  
   213  		fees, err := tequilapiProvider.GetTransactorFees()
   214  		assert.NoError(t, err)
   215  
   216  		earningsByHermes := make(map[common.Address]*big.Int)
   217  		for _, c := range consumersToTest {
   218  			copy := earningsByHermes[c.hermesID]
   219  
   220  			if copy == nil {
   221  				copy = new(big.Int)
   222  			}
   223  
   224  			copy = new(big.Int).Add(copy, c.balanceSpent)
   225  			earningsByHermes[c.hermesID] = copy
   226  		}
   227  		hermesOneEarnings := earningsByHermes[common.HexToAddress(hermesID)]
   228  		hermesTwoEarnings := earningsByHermes[common.HexToAddress(hermes2ID)]
   229  		totalEarnings := new(big.Int).Add(hermesOneEarnings, hermesTwoEarnings)
   230  		fdiff := getDiffFloat(providerStatus.EarningsTotal, totalEarnings)
   231  		assert.True(t, fdiff < tenthThou)
   232  
   233  		hic, err := bindings.NewHermesImplementationCaller(common.HexToAddress(hermesID), ethClient)
   234  		assert.NoError(t, err)
   235  
   236  		hermesFee, err := hic.CalculateHermesFee(&bind.CallOpts{}, totalEarnings)
   237  		assert.NoError(t, err)
   238  
   239  		feeSum := big.NewInt(0).Add(big.NewInt(0).Add(fees.Settlement, hermesFee), fees.Settlement)
   240  		expected := new(big.Int).Sub(totalEarnings, feeSum)
   241  		balance, err := caller.BalanceOf(&bind.CallOpts{}, common.HexToAddress(beneficiary))
   242  		assert.NoError(t, err)
   243  
   244  		diff := getDiffFloat(balance, expected)
   245  		diff = math.Abs(diff)
   246  
   247  		assert.True(t, diff >= 0 && diff <= tenthThou, fmt.Sprintf("got diff %v", diff))
   248  	})
   249  
   250  	t.Run("Provider withdraws to l1", func(t *testing.T) {
   251  		// since we've changed the benef, our channel is empty. Pretend that we do have myst in it.
   252  		chid, err := crypto.GenerateChannelAddress(providerID, hermes2ID, registryAddress, channelImplementation)
   253  		assert.NoError(t, err)
   254  		mintMyst(t, crypto.FloatToBigMyst(1), common.HexToAddress(chid), ethClientL2)
   255  
   256  		beneficiary := common.HexToAddress("0x1231adadadadaadada123123")
   257  		caller, err := bindings.NewMystTokenCaller(common.HexToAddress(mystAddress), ethClient)
   258  		assert.NoError(t, err)
   259  
   260  		balance, err := caller.BalanceOf(&bind.CallOpts{}, beneficiary)
   261  		assert.NoError(t, err)
   262  		assert.Equal(t, big.NewInt(0).Uint64(), balance.Uint64())
   263  
   264  		err = tequilapiProvider.Withdraw(identity.FromAddress(providerID), common.HexToAddress(hermes2ID), beneficiary, nil, chainID, 0)
   265  		assert.NoError(t, err)
   266  
   267  		assert.Eventually(t, func() bool {
   268  			balance, _ := caller.BalanceOf(&bind.CallOpts{}, beneficiary)
   269  			return balance.Cmp(big.NewInt(0)) == 1
   270  		}, time.Second*20, time.Millisecond*100)
   271  	})
   272  
   273  	t.Run("Provider stops services", func(t *testing.T) {
   274  		services, err := tequilapiProvider.Services()
   275  		assert.NoError(t, err)
   276  
   277  		for _, service := range services {
   278  			err := tequilapiProvider.ServiceStop(service.ID)
   279  			assert.NoError(t, err)
   280  		}
   281  	})
   282  
   283  	t.Run("Provider starts whitelisted noop services", func(t *testing.T) {
   284  		req := contract.ServiceStartRequest{
   285  			ProviderID:     providerID,
   286  			Type:           "noop",
   287  			AccessPolicies: &contract.ServiceAccessPolicies{IDs: []string{"mysterium"}},
   288  		}
   289  
   290  		_, err := tequilapiProvider.ServiceStart(req)
   291  		assert.NoError(t, err)
   292  	})
   293  
   294  	t.Run("Whitelisted consumer connects to the whitelisted noop service", func(t *testing.T) {
   295  		c := consumersToTest[0]
   296  
   297  		topUpConsumer(t, "0xc4cb9a91b8498776f6f8a0d5a2a23beec9b3cef3", common.HexToAddress(hermesID), registrationFee)
   298  
   299  		consumerRegistrationFlow(t, c.tequila(), "0xc4cb9a91b8498776f6f8a0d5a2a23beec9b3cef3", "")
   300  
   301  		proposal := consumerPicksProposal(t, c.tequila(), "noop")
   302  		consumerConnectFlow(t, c.tequila(), "0xc4cb9a91b8498776f6f8a0d5a2a23beec9b3cef3", hermesID, "noop", proposal)
   303  	})
   304  
   305  	t.Run("Consumers rejected by whitelisted service", func(t *testing.T) {
   306  		c := consumersToTest[0]
   307  		proposal := consumerPicksProposal(t, c.tequila(), c.serviceType)
   308  		consumerRejectWhitelistedFlow(t, c.tequila(), c.consumerID, hermesID, c.serviceType, proposal)
   309  	})
   310  
   311  	t.Run("Registration with bounty", func(t *testing.T) {
   312  		t.Run("consumer", func(t *testing.T) {
   313  			c := consumersToTest[0]
   314  			id, err := c.tequila().NewIdentity("")
   315  			assert.NoError(t, err)
   316  
   317  			err = c.tequila().Unlock(id.Address, "")
   318  			assert.NoError(t, err)
   319  
   320  			status, err := c.tequila().IdentityRegistrationStatus(id.Address)
   321  			assert.NoError(t, err)
   322  			assert.Equal(t, "Unregistered", status.Status)
   323  
   324  			err = c.tequila().RegisterIdentity(id.Address, "", nil)
   325  			assert.NoError(t, err)
   326  
   327  			assert.Eventually(t, func() bool {
   328  				status, err := c.tequila().IdentityRegistrationStatus(id.Address)
   329  				if err != nil {
   330  					return false
   331  				}
   332  				return status.Status == "Registered"
   333  			}, time.Second*20, time.Millisecond*100)
   334  		})
   335  		t.Run("provider", func(t *testing.T) {
   336  			c := consumersToTest[0]
   337  			id, err := c.tequila().NewIdentity("")
   338  			assert.NoError(t, err)
   339  
   340  			err = c.tequila().Unlock(id.Address, "")
   341  			assert.NoError(t, err)
   342  
   343  			status, err := c.tequila().IdentityRegistrationStatus(id.Address)
   344  			assert.NoError(t, err)
   345  			assert.Equal(t, "Unregistered", status.Status)
   346  
   347  			err = c.tequila().RegisterIdentity(id.Address, "", nil)
   348  			assert.NoError(t, err)
   349  
   350  			assert.Eventually(t, func() bool {
   351  				status, err := c.tequila().IdentityRegistrationStatus(id.Address)
   352  				if err != nil {
   353  					return false
   354  				}
   355  				return status.Status == "Registered"
   356  			}, time.Second*20, time.Millisecond*100)
   357  		})
   358  	})
   359  }
   360  
   361  func recheckBalancesWithHermes(t *testing.T, consumerID string, consumerSpending *big.Int, serviceType, hermesURL string) {
   362  	var testSuccess bool
   363  	var lastHermes *big.Int
   364  	assert.Eventually(t, func() bool {
   365  		hermesCaller := pingpong.NewHermesCaller(requests.NewHTTPClient("0.0.0.0", time.Second), hermesURL)
   366  		hermesData, err := hermesCaller.GetConsumerData(chainID, consumerID, -1)
   367  		assert.NoError(t, err)
   368  		promised := hermesData.LatestPromise.Amount
   369  		lastHermes = promised
   370  
   371  		// Author: vkuznecovas
   372  		// Due to the async nature of the payment system, a situation might occur where a consumers reported spending is larger than the actual amount that reaches hermes.
   373  		// This happens in the following flow:
   374  		// 1) Session is established.
   375  		// 2) Payments occur normally for some time.
   376  		// 3) Consumer received yet another invoice.
   377  		// 4) Consumer starts calculating the amount to promise.
   378  		// 5) Session is killed as operation 4) is happening.
   379  		// 6) The promise is incremented but never issued as the session is aborted. This can happen due to promise not being sent, or provider not listening for further promises on the given topic.
   380  		// 7) Hermes is not aware of the last promise, therefore the consumer and hermes reportings are different.
   381  		// I do not believe this will cause issues in reality, as such situations occur in e2e test regularly due to the rapid exchange of promises.
   382  		// Under normal circumstances, such occurences should be VERY, VERY rare and the amount of myst involved is rather small. They should have no impact on the payment system as a whole.
   383  		// Therefore, for these tests to be stable, the following solution is proposed:
   384  		// Make sure that the hermes and consumer reported spendings differ by no more than 1/100000 of a myst.
   385  		absDiffFloat := getDiffFloat(consumerSpending, promised)
   386  		res := absDiffFloat < tenthThou
   387  		if res {
   388  			testSuccess = true
   389  		}
   390  		return res
   391  	}, time.Second*20, time.Millisecond*300)
   392  
   393  	if !testSuccess {
   394  		fmt.Printf("Consumer reported spending %v hermes says %v. Service type %v. Hermes url %v, consumer ID %v", consumerSpending, lastHermes, serviceType, hermesURL, consumerID)
   395  	}
   396  }
   397  
   398  func getDiffFloat(a, b *big.Int) float64 {
   399  	diff := new(big.Int).Sub(a, b)
   400  	absoluteDiff := new(big.Int).Abs(diff)
   401  	return crypto.BigMystToFloat(absoluteDiff)
   402  }
   403  
   404  func identityCreateFlow(t *testing.T, tequilapi *tequilapi_client.Client, idPassphrase string) string {
   405  	id, err := tequilapi.NewIdentity(idPassphrase)
   406  	assert.NoError(t, err)
   407  	log.Info().Msg("Created new identity: " + id.Address)
   408  
   409  	return id.Address
   410  }
   411  
   412  func initEthClient(t *testing.T) {
   413  	addr := common.HexToAddress(addressForTopups)
   414  	ks := keystore.NewKeyStore("/node/keystore", keystore.StandardScryptN, keystore.StandardScryptP)
   415  	acc, err := ks.Find(accounts.Account{Address: addr})
   416  	assert.NoError(t, err)
   417  
   418  	err = ks.Unlock(acc, "")
   419  	assert.NoError(t, err)
   420  
   421  	c, err := ethclient.Dial("http://ganache:8545")
   422  	assert.NoError(t, err)
   423  
   424  	ethClient = c
   425  
   426  	c2, err := ethclient.Dial("ws://ganache2:8545")
   427  	assert.NoError(t, err)
   428  	ethClientL2 = c2
   429  
   430  	ethSignerBuilder = func(client *ethclient.Client) func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
   431  		chainId, err := client.ChainID(context.Background())
   432  		if err != nil {
   433  			return func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
   434  				return nil, err
   435  			}
   436  		}
   437  		return func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
   438  			return ks.SignTx(acc, tx, chainId)
   439  		}
   440  	}
   441  
   442  }
   443  
   444  func mintMyst(t *testing.T, amount *big.Int, chid common.Address, ethc *ethclient.Client) {
   445  	ts, err := bindings.NewMystTokenTransactor(common.HexToAddress(mystAddress), ethc)
   446  	assert.NoError(t, err)
   447  
   448  	nonce, err := ethc.PendingNonceAt(context.Background(), common.HexToAddress(addressForTopups))
   449  	assert.NoError(t, err)
   450  
   451  	_, err = ts.Transfer(&bind.TransactOpts{
   452  		From:   common.HexToAddress(addressForTopups),
   453  		Signer: ethSignerBuilder(ethc),
   454  		Nonce:  big.NewInt(0).SetUint64(nonce),
   455  	}, chid, amount)
   456  	assert.NoError(t, err)
   457  }
   458  
   459  func providerRegistrationFlow(t *testing.T, tequilapi *tequilapi_client.Client, id, idPassphrase string) {
   460  	err := tequilapi.Unlock(id, idPassphrase)
   461  	assert.NoError(t, err)
   462  
   463  	err = tequilapi.RegisterIdentity(id, "", nil)
   464  	assert.True(t, err == nil || assert.Contains(t, err.Error(), "registration in progress"))
   465  
   466  	assert.Eventually(t, func() bool {
   467  		idStatus, _ := tequilapi.Identity(id)
   468  		return idStatus.RegistrationStatus == "Registered"
   469  	}, time.Second*30, time.Millisecond*500)
   470  
   471  	// once we're registered, check some other information
   472  	idStatus, err := tequilapi.Identity(id)
   473  	assert.NoError(t, err)
   474  
   475  	assert.Equal(t, "Registered", idStatus.RegistrationStatus)
   476  	assert.Equal(t, providerChannelAddress, idStatus.ChannelAddress)
   477  	assert.Eventually(t, func() bool {
   478  		balance, err := tequilapi.BalanceRefresh(id)
   479  		assert.NoError(t, err)
   480  		return balanceAfterRegistration.Cmp(balance.Balance) == 0
   481  	}, time.Second*20, time.Millisecond*500)
   482  	assert.Zero(t, idStatus.Earnings.Uint64())
   483  	assert.Zero(t, idStatus.EarningsTotal.Uint64())
   484  }
   485  
   486  func topUpConsumer(t *testing.T, id string, hermesID common.Address, registrationFee *big.Int) {
   487  	// TODO: once free registration is a thing of the past, remove this return
   488  
   489  	// chid, err := crypto.GenerateChannelAddress(id, hermesID.Hex(), registryAddress, channelImplementation)
   490  	// assert.NoError(t, err)
   491  
   492  	// // add some balance for fees + consuming service
   493  	// amountToTopUp := big.NewInt(0).Mul(registrationFee, big.NewInt(20))
   494  	// mintMyst(t, amountToTopUp, common.HexToAddress(chid))
   495  }
   496  
   497  func consumerRegistrationFlow(t *testing.T, tequilapi *tequilapi_client.Client, id, idPassphrase string) {
   498  	err := tequilapi.Unlock(id, idPassphrase)
   499  	assert.NoError(t, err)
   500  
   501  	err = tequilapi.RegisterIdentity(id, "", nil)
   502  	assert.NoError(t, err)
   503  
   504  	// now we check identity again
   505  	err = waitForCondition(func() (bool, error) {
   506  		regStatus, err := tequilapi.IdentityRegistrationStatus(id)
   507  		return regStatus.Registered, err
   508  	})
   509  	assert.NoError(t, err)
   510  
   511  	idStatus, err := tequilapi.Identity(id)
   512  	assert.NoError(t, err)
   513  	assert.Equal(t, "Registered", idStatus.RegistrationStatus)
   514  	assert.Eventually(t, func() bool {
   515  		balance, err := tequilapi.BalanceRefresh(id)
   516  		assert.NoError(t, err)
   517  		return balanceAfterRegistration.Cmp(balance.Balance) == 0
   518  	}, time.Second*20, time.Millisecond*500)
   519  	assert.Zero(t, idStatus.Earnings.Uint64())
   520  	assert.Zero(t, idStatus.EarningsTotal.Uint64())
   521  }
   522  
   523  // expect exactly one proposal
   524  func consumerPicksProposal(t *testing.T, tequilapi *tequilapi_client.Client, serviceType string) contract.ProposalDTO {
   525  	var proposals []contract.ProposalDTO
   526  	assert.Eventually(t, func() bool {
   527  		p, stateErr := tequilapi.ProposalsByTypeWithWhitelisting(serviceType)
   528  		if stateErr != nil {
   529  			log.Err(stateErr)
   530  			return false
   531  		}
   532  		proposals = p
   533  		return len(p) == 1
   534  	}, time.Second*30, time.Millisecond*200)
   535  
   536  	log.Info().Msgf("Selected proposal is: %v, serviceType=%v", proposals[0], serviceType)
   537  	return proposals[0]
   538  }
   539  
   540  func consumerConnectFlow(t *testing.T, tequilapi *tequilapi_client.Client, consumerID, hermesID, serviceType string, proposal contract.ProposalDTO) *big.Int {
   541  	connectionStatus, err := tequilapi.ConnectionStatus(0)
   542  	assert.NoError(t, err)
   543  	assert.Equal(t, "NotConnected", connectionStatus.Status)
   544  
   545  	_, err = tequilapi.ConnectionIP()
   546  	assert.NoError(t, err)
   547  
   548  	err = waitForCondition(func() (bool, error) {
   549  		status, err := tequilapi.ConnectionStatus(0)
   550  		return status.Status == "NotConnected", err
   551  	})
   552  	assert.NoError(t, err)
   553  
   554  	connectionStatus, err = tequilapi.ConnectionCreate(consumerID, proposal.ProviderID, hermesID, serviceType, contract.ConnectOptions{
   555  		DisableKillSwitch: false,
   556  	})
   557  
   558  	assert.NoError(t, err)
   559  
   560  	err = waitForCondition(func() (bool, error) {
   561  		status, err := tequilapi.ConnectionStatus(0)
   562  		return status.Status == "Connected", err
   563  	})
   564  	assert.NoError(t, err)
   565  
   566  	_, err = tequilapi.ConnectionIP()
   567  	assert.NoError(t, err)
   568  
   569  	// sessions history should be created after connect
   570  	sessionsDTO, err := tequilapi.SessionsByServiceType(serviceType)
   571  	assert.NoError(t, err)
   572  
   573  	require.True(t, len(sessionsDTO.Items) >= 1)
   574  	se := sessionsDTO.Items[0]
   575  	assert.Equal(t, "e2e-land", se.ProviderCountry)
   576  	assert.Equal(t, serviceType, se.ServiceType)
   577  	assert.Equal(t, proposal.ProviderID, se.ProviderID)
   578  	assert.Equal(t, connectionStatus.SessionID, se.ID)
   579  	assert.Equal(t, "New", se.Status)
   580  
   581  	// Wait some time for session to collect stats.
   582  	assert.Eventually(t, sessionStatsReceived(tequilapi, serviceType), 60*time.Second, 1*time.Second, serviceType)
   583  
   584  	err = tequilapi.ConnectionDestroy(0)
   585  	assert.NoError(t, err)
   586  
   587  	err = waitForCondition(func() (bool, error) {
   588  		status, err := tequilapi.ConnectionStatus(0)
   589  		return status.Status == "NotConnected", err
   590  	})
   591  	assert.NoError(t, err)
   592  
   593  	// sessions history should be updated after disconnect
   594  	sessionsDTO, err = tequilapi.SessionsByServiceType(serviceType)
   595  	assert.NoError(t, err)
   596  
   597  	assert.True(t, len(sessionsDTO.Items) >= 1)
   598  	se = sessionsDTO.Items[0]
   599  	assert.Equal(t, "Completed", se.Status)
   600  
   601  	// call the custom asserter for the given service type
   602  	serviceTypeAssertionMap[serviceType](t, se)
   603  	consumerStatus := contract.IdentityDTO{}
   604  	assert.Eventually(t, func() bool {
   605  		cs, err := tequilapi.Identity(consumerID)
   606  		if err != nil {
   607  			return false
   608  		}
   609  		consumerStatus = cs
   610  		return true
   611  	}, time.Second*20, time.Millisecond*150)
   612  	assert.True(t, consumerStatus.Balance.Cmp(big.NewInt(0)) == 1, "consumer balance should not be empty")
   613  	assert.True(t, consumerStatus.Balance.Cmp(balanceAfterRegistration) == -1, "balance should decrease but is %s", consumerStatus.Balance)
   614  	assert.Zero(t, consumerStatus.Earnings.Uint64())
   615  	assert.Zero(t, consumerStatus.EarningsTotal.Uint64())
   616  
   617  	return new(big.Int).Sub(balanceAfterRegistration, consumerStatus.Balance)
   618  }
   619  
   620  func consumerRejectWhitelistedFlow(t *testing.T, tequilapi *tequilapi_client.Client, consumerID, accountantID, serviceType string, proposal contract.ProposalDTO) {
   621  	connectionStatus, err := tequilapi.ConnectionStatus(0)
   622  	assert.NoError(t, err)
   623  	assert.Equal(t, "NotConnected", connectionStatus.Status)
   624  
   625  	nonVpnIP, err := tequilapi.ConnectionIP()
   626  	assert.NoError(t, err)
   627  	log.Info().Msg("Original consumer IP: " + nonVpnIP.IP)
   628  
   629  	err = waitForCondition(func() (bool, error) {
   630  		status, err := tequilapi.ConnectionStatus(0)
   631  		return status.Status == "NotConnected", err
   632  	})
   633  	assert.NoError(t, err)
   634  
   635  	_, err = tequilapi.ConnectionCreate(consumerID, proposal.ProviderID, accountantID, serviceType, contract.ConnectOptions{})
   636  	assert.Error(t, err)
   637  	assert.Contains(t, err.Error(), "consumer identity is not allowed")
   638  }
   639  
   640  func providerEarnedTokens(t *testing.T, tequilapi *tequilapi_client.Client, id string, earningsExpected *big.Int) *big.Int {
   641  	// Before settlement
   642  	assert.Eventually(t, func() bool {
   643  		providerStatus, err := tequilapi.Identity(id)
   644  		if err != nil {
   645  			return false
   646  		}
   647  
   648  		fdiff := getDiffFloat(providerStatus.Earnings, earningsExpected)
   649  		return fdiff < tenthThou
   650  	}, time.Second*20, time.Millisecond*250)
   651  
   652  	var providerStatus contract.IdentityDTO
   653  	var err error
   654  	assert.Eventually(t, func() bool {
   655  		providerStatus, err = tequilapi.Identity(id)
   656  		assert.NoError(t, err)
   657  		return providerStatus.Balance.Cmp(balanceAfterRegistration) == 0
   658  	}, time.Second*20, time.Millisecond*500)
   659  
   660  	// For reasoning behind these, see the comment in recheckBalancesWithHermes
   661  	actualEarnings := getDiffFloat(earningsExpected, providerStatus.Earnings)
   662  	assert.True(t, actualEarnings < tenthThou)
   663  
   664  	actualEarnings = getDiffFloat(earningsExpected, providerStatus.EarningsTotal)
   665  	assert.True(t, actualEarnings < tenthThou)
   666  
   667  	assert.True(t, providerStatus.Earnings.Cmp(big.NewInt(500)) == 1, "earnings should be at least 500 but is %d", providerStatus.Earnings)
   668  	return providerStatus.Earnings
   669  }
   670  
   671  func sessionStatsReceived(tequilapi *tequilapi_client.Client, serviceType string) func() bool {
   672  	var delegate func(stats contract.ConnectionStatisticsDTO) bool
   673  	if serviceType != "noop" {
   674  		delegate = func(stats contract.ConnectionStatisticsDTO) bool {
   675  			return stats.BytesReceived > 0 && stats.BytesSent > 0 && stats.Duration > 45
   676  		}
   677  	} else {
   678  		delegate = func(stats contract.ConnectionStatisticsDTO) bool {
   679  			return stats.Duration > 45
   680  		}
   681  	}
   682  
   683  	return func() bool {
   684  		stats, err := tequilapi.ConnectionStatistics()
   685  		if err != nil {
   686  			return false
   687  		}
   688  		return delegate(stats)
   689  	}
   690  }
   691  
   692  type sessionAsserter func(t *testing.T, session contract.SessionDTO)
   693  
   694  var serviceTypeAssertionMap = map[string]sessionAsserter{
   695  	"openvpn": func(t *testing.T, session contract.SessionDTO) {
   696  		assert.NotZero(t, session.Duration)
   697  		assert.NotZero(t, session.BytesSent)
   698  		assert.NotZero(t, session.BytesReceived)
   699  	},
   700  	"noop": func(t *testing.T, session contract.SessionDTO) {
   701  		assert.NotZero(t, session.Duration)
   702  		assert.Zero(t, session.BytesSent)
   703  		assert.Zero(t, session.BytesReceived)
   704  	},
   705  	"wireguard": func(t *testing.T, session contract.SessionDTO) {
   706  		assert.NotZero(t, session.Duration)
   707  		assert.NotZero(t, session.BytesSent)
   708  		assert.NotZero(t, session.BytesReceived)
   709  	},
   710  }