gitlab.com/jokerrs1/Sia@v1.3.2/node/api/ecosystem_helpers_test.go (about)

     1  package api
     2  
     3  // ecosystem_helpers_test.go has a bunch of helper functions to make setting up
     4  // large ecosystem tests easier.
     5  //
     6  // List of helper functions:
     7  //    addStorageToAllHosts // adds a storage folder to every host
     8  //    announceAllHosts     // announce all hosts to the network (and mine a block)
     9  //    fullyConnectNodes    // connects each server tester to all the others
    10  //    fundAllNodes         // mines blocks until all server testers have money
    11  //    synchronizationCheck // checks that all server testers have the same recent block
    12  //    waitForBlock         // block until the provided block is the most recent block for all server testers
    13  
    14  import (
    15  	"errors"
    16  	"fmt"
    17  	"net/url"
    18  	"time"
    19  
    20  	"github.com/NebulousLabs/Sia/types"
    21  )
    22  
    23  // addStorageToAllHosts adds a storage folder with a bunch of storage to each
    24  // host.
    25  func addStorageToAllHosts(sts []*serverTester) error {
    26  	for _, st := range sts {
    27  		values := url.Values{}
    28  		values.Set("path", st.dir)
    29  		values.Set("size", "1048576")
    30  		err := st.stdPostAPI("/host/storage/folders/add", values)
    31  		if err != nil {
    32  			return err
    33  		}
    34  	}
    35  	return nil
    36  }
    37  
    38  // announceAllHosts will announce every host in the tester set to the
    39  // blockchain.
    40  func announceAllHosts(sts []*serverTester) error {
    41  	// Check that all announcements will be on the same chain.
    42  	_, err := synchronizationCheck(sts)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	// Grab the initial transaction pool size to know how many total transactions
    48  	// there should be after announcement.
    49  	initialTpoolSize := len(sts[0].tpool.TransactionList())
    50  
    51  	// Announce each host.
    52  	for _, st := range sts {
    53  		// Set the host to be accepting contracts.
    54  		acceptingContractsValues := url.Values{}
    55  		acceptingContractsValues.Set("acceptingcontracts", "true")
    56  		err = st.stdPostAPI("/host", acceptingContractsValues)
    57  		if err != nil {
    58  			return err
    59  		}
    60  
    61  		// Fetch the host net address.
    62  		var hg HostGET
    63  		err = st.getAPI("/host", &hg)
    64  		if err != nil {
    65  			return err
    66  		}
    67  
    68  		// Make the announcement.
    69  		announceValues := url.Values{}
    70  		announceValues.Set("address", string(hg.ExternalSettings.NetAddress))
    71  		err = st.stdPostAPI("/host/announce", announceValues)
    72  		if err != nil {
    73  			return err
    74  		}
    75  	}
    76  
    77  	// Wait until all of the transactions have propagated to all of the nodes.
    78  	//
    79  	// TODO: Replace this direct transaction pool call with a call to the
    80  	// /transactionpool endpoint.
    81  	//
    82  	// TODO: At some point the number of transactions needed to make an
    83  	// announcement may change. Currently its 2.
    84  	for i := 0; i < 50; i++ {
    85  		if len(sts[0].tpool.TransactionList()) == len(sts)*2+initialTpoolSize {
    86  			break
    87  		}
    88  		time.Sleep(time.Millisecond * 100)
    89  	}
    90  	if len(sts[0].tpool.TransactionList()) < len(sts)*2+initialTpoolSize {
    91  		return fmt.Errorf("Host announcements do not seem to have propagated to the leader's tpool: %v, %v, %v", len(sts), len(sts[0].tpool.TransactionList())+initialTpoolSize, initialTpoolSize)
    92  	}
    93  
    94  	// Mine a block and then wait for all of the nodes to syncrhonize to it.
    95  	_, err = sts[0].miner.AddBlock()
    96  	if err != nil {
    97  		return err
    98  	}
    99  	// Block until the block propagated to all nodes
   100  	for _, st := range sts[1:] {
   101  		err = waitForBlock(sts[0].cs.CurrentBlock().ID(), st)
   102  		if err != nil {
   103  			return (err)
   104  		}
   105  	}
   106  	// Check if all nodes are on the same block now
   107  	_, err = synchronizationCheck(sts)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	// Block until every node has completed the scan of every other node, so
   113  	// that each node has a full hostdb.
   114  	for _, st := range sts {
   115  		var ah HostdbActiveGET
   116  		for i := 0; i < 100; i++ {
   117  			err = st.getAPI("/hostdb/active", &ah)
   118  			if err != nil {
   119  				return err
   120  			}
   121  			if len(ah.Hosts) >= len(sts) {
   122  				break
   123  			}
   124  			time.Sleep(time.Millisecond * 100)
   125  		}
   126  		if len(ah.Hosts) < len(sts) {
   127  			return errors.New("one of the nodes hostdbs was unable to find at least one host announcement")
   128  		}
   129  	}
   130  	return nil
   131  }
   132  
   133  // fullyConnectNodes takes a bunch of tester nodes and connects each to the
   134  // other, creating a fully connected graph so that everyone is on the same
   135  // chain.
   136  //
   137  // After connecting the nodes, it verifies that all the nodes have
   138  // synchronized.
   139  func fullyConnectNodes(sts []*serverTester) error {
   140  	for i, sta := range sts {
   141  		var gg GatewayGET
   142  		err := sta.getAPI("/gateway", &gg)
   143  		if err != nil {
   144  			return err
   145  		}
   146  
   147  		// Connect this node to every other node.
   148  		for _, stb := range sts[i+1:] {
   149  			// Try connecting to the other node until both have the other in
   150  			// their peer list.
   151  			err = retry(100, time.Millisecond*100, func() error {
   152  				// NOTE: this check depends on string-matching an error in the
   153  				// gateway. If that error changes at all, this string will need to
   154  				// be updated.
   155  				err := stb.stdPostAPI("/gateway/connect/"+string(gg.NetAddress), nil)
   156  				if err != nil && err.Error() != "already connected to this peer" {
   157  					return err
   158  				}
   159  
   160  				// Check that the gateways are connected.
   161  				bToA := false
   162  				aToB := false
   163  				var ggb GatewayGET
   164  				err = stb.getAPI("/gateway", &ggb)
   165  				if err != nil {
   166  					return err
   167  				}
   168  				for _, peer := range ggb.Peers {
   169  					if peer.NetAddress == gg.NetAddress {
   170  						bToA = true
   171  						break
   172  					}
   173  				}
   174  				err = sta.getAPI("/gateway", &gg)
   175  				if err != nil {
   176  					return err
   177  				}
   178  				for _, peer := range gg.Peers {
   179  					if peer.NetAddress == ggb.NetAddress {
   180  						aToB = true
   181  						break
   182  					}
   183  				}
   184  				if !aToB || !bToA {
   185  					return fmt.Errorf("called connect between two nodes, but they are not peers: %v %v %v %v %v %v", aToB, bToA, gg.NetAddress, ggb.NetAddress, gg.Peers, ggb.Peers)
   186  				}
   187  				return nil
   188  
   189  			})
   190  			if err != nil {
   191  				return err
   192  			}
   193  		}
   194  	}
   195  
   196  	// Perform a synchronization check.
   197  	_, err := synchronizationCheck(sts)
   198  	return err
   199  }
   200  
   201  // fundAllNodes will make sure that each node has mined a block in the longest
   202  // chain, then will mine enough blocks that the miner payouts manifest in the
   203  // wallets of each node.
   204  func fundAllNodes(sts []*serverTester) error {
   205  	// Check that all of the nodes are synchronized.
   206  	chainTip, err := synchronizationCheck(sts)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	// Mine a block for each node to fund their wallet.
   212  	for i := range sts {
   213  		err := waitForBlock(chainTip, sts[i])
   214  		if err != nil {
   215  			return err
   216  		}
   217  
   218  		// Mine a block. The next iteration of this loop will ensure that the
   219  		// block propagates and does not get orphaned.
   220  		block, err := sts[i].miner.AddBlock()
   221  		if err != nil {
   222  			return err
   223  		}
   224  		chainTip = block.ID()
   225  	}
   226  
   227  	// Wait until the chain tip has propagated to the first node.
   228  	err = waitForBlock(chainTip, sts[0])
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	// Mine types.MaturityDelay more blocks from the final node to mine a
   234  	// block, to guarantee that all nodes have had their payouts mature, such
   235  	// that their wallets can begin spending immediately.
   236  	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
   237  		_, err := sts[0].miner.AddBlock()
   238  		if err != nil {
   239  			return err
   240  		}
   241  	}
   242  
   243  	// Block until every node has the full chain.
   244  	_, err = synchronizationCheck(sts)
   245  	return err
   246  }
   247  
   248  // synchronizationCheck takes a bunch of server testers as input and checks
   249  // that they all have the same current block as the first server tester. The
   250  // first server tester needs to have the most recent block in order for the
   251  // check to work.
   252  func synchronizationCheck(sts []*serverTester) (types.BlockID, error) {
   253  	// Prefer returning an error in the event of a zero-length server tester -
   254  	// an error should be returned if the developer accidentally uses a nil
   255  	// slice instead of whatever value was intended, and there's no reason to
   256  	// check for synchronization if there aren't any nodes to be synchronized.
   257  	if len(sts) == 0 {
   258  		return types.BlockID{}, errors.New("no server testers provided")
   259  	}
   260  
   261  	// Wait until all nodes are on the same block
   262  	for _, st := range sts[1:] {
   263  		err := waitForBlock(sts[0].cs.CurrentBlock().ID(), st)
   264  		if err != nil {
   265  			return types.BlockID{}, err
   266  		}
   267  	}
   268  
   269  	var cg ConsensusGET
   270  	err := sts[0].getAPI("/consensus", &cg)
   271  	if err != nil {
   272  		return types.BlockID{}, err
   273  	}
   274  	leaderBlockID := cg.CurrentBlock
   275  	for i := range sts {
   276  		// Spin until the current block matches the leader block.
   277  		success := false
   278  		for j := 0; j < 100; j++ {
   279  			err = sts[i].getAPI("/consensus", &cg)
   280  			if err != nil {
   281  				return types.BlockID{}, err
   282  			}
   283  			if cg.CurrentBlock == leaderBlockID {
   284  				success = true
   285  				break
   286  			}
   287  			time.Sleep(time.Millisecond * 100)
   288  		}
   289  		if !success {
   290  			return types.BlockID{}, errors.New("synchronization check failed - nodes do not seem to be synchronized")
   291  		}
   292  	}
   293  	return leaderBlockID, nil
   294  }
   295  
   296  // waitForBlock will block until the provided chain tip is the most recent
   297  // block in the provided testing node.
   298  func waitForBlock(chainTip types.BlockID, st *serverTester) error {
   299  	var cg ConsensusGET
   300  	success := false
   301  	for j := 0; j < 100; j++ {
   302  		err := st.getAPI("/consensus", &cg)
   303  		if err != nil {
   304  			return err
   305  		}
   306  		if cg.CurrentBlock == chainTip {
   307  			success = true
   308  			break
   309  		}
   310  		time.Sleep(time.Millisecond * 100)
   311  	}
   312  	if !success {
   313  		return errors.New("node never reached the correct chain tip")
   314  	}
   315  	return nil
   316  }