github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/integration/core/kernel_test.go (about)

     1  // +build integration
     2  
     3  package core
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"syscall"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hyperledger/burrow/acm"
    15  	"github.com/hyperledger/burrow/acm/balance"
    16  	"github.com/hyperledger/burrow/config"
    17  	"github.com/hyperledger/burrow/event"
    18  	"github.com/hyperledger/burrow/execution/exec"
    19  	"github.com/hyperledger/burrow/execution/solidity"
    20  	"github.com/hyperledger/burrow/genesis"
    21  	"github.com/hyperledger/burrow/integration"
    22  	"github.com/hyperledger/burrow/integration/rpctest"
    23  	"github.com/hyperledger/burrow/keys"
    24  	"github.com/hyperledger/burrow/logging/logconfig"
    25  	"github.com/hyperledger/burrow/logging/loggers"
    26  	"github.com/hyperledger/burrow/rpc/rpctransact"
    27  	"github.com/hyperledger/burrow/txs/payload"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"google.golang.org/grpc/codes"
    31  	"google.golang.org/grpc/status"
    32  )
    33  
    34  func TestKernel(t *testing.T) {
    35  	testKernel(t)
    36  }
    37  
    38  func TestKernelNoConsensus(t *testing.T) {
    39  	testKernel(t, integration.NoConsensus)
    40  }
    41  
    42  func testKernel(t *testing.T, opts ...func(*config.BurrowConfig)) {
    43  	t.Run(fmt.Sprintf("Group"), func(t *testing.T) {
    44  		t.Parallel()
    45  		genesisDoc, privateAccounts, privateValidators := genesis.NewDeterministicGenesis(123).GenesisDoc(1, 1)
    46  		require.NotNil(t, privateAccounts)
    47  		require.NotNil(t, privateValidators)
    48  		t.Run("BootThenShutdown", func(t *testing.T) {
    49  			conf, cleanup := integration.NewTestConfig(genesisDoc, opts...)
    50  			defer cleanup()
    51  			require.NotNil(t, privateAccounts)
    52  			require.NotNil(t, privateValidators)
    53  			assert.NoError(t, bootWaitBlocksShutdown(t, privateValidators[0], privateAccounts, conf, nil))
    54  		})
    55  
    56  		t.Run("BootShutdownResume", func(t *testing.T) {
    57  			testConfig, cleanup := integration.NewTestConfig(genesisDoc, opts...)
    58  			defer cleanup()
    59  			i := uint64(0)
    60  			// asserts we get a consecutive run of blocks
    61  			blockChecker := func(block *exec.BlockExecution) bool {
    62  				if i == 0 {
    63  					// We send some synchronous transactions so catch up to latest block
    64  					i = block.Height - 1
    65  				}
    66  				require.Equal(t, i+1, block.Height)
    67  				i++
    68  				// stop every third block
    69  				if i%3 == 0 {
    70  					i = 0
    71  					return false
    72  				}
    73  				return true
    74  			}
    75  			// First run
    76  			err := bootWaitBlocksShutdown(t, privateValidators[0], privateAccounts, testConfig, blockChecker)
    77  			require.NoError(t, err)
    78  			// Resume and check we pick up where we left off
    79  			err = bootWaitBlocksShutdown(t, privateValidators[0], privateAccounts, testConfig, blockChecker)
    80  			require.NoError(t, err)
    81  			// Resuming with mismatched genesis should fail
    82  			genesisDoc.Salt = []byte("foo")
    83  			err = bootWaitBlocksShutdown(t, privateValidators[0], privateAccounts, testConfig, blockChecker)
    84  			assert.Error(t, err)
    85  		})
    86  
    87  		t.Run("LoggingSignals", func(t *testing.T) {
    88  			conf, cleanup := integration.NewTestConfig(genesisDoc, opts...)
    89  			defer cleanup()
    90  			name := "capture"
    91  			buffer := 100
    92  			path := "foo.json"
    93  			conf.Logging = logconfig.New().
    94  				Root(func(sink *logconfig.SinkConfig) *logconfig.SinkConfig {
    95  					return sink.SetTransform(logconfig.CaptureTransform(name, buffer, false)).
    96  						SetOutput(logconfig.FileOutput(path).SetFormat(loggers.JSONFormat))
    97  				})
    98  			i := 0
    99  			gap := 1
   100  			assert.NoError(t, bootWaitBlocksShutdown(t, privateValidators[0], privateAccounts, conf,
   101  				func(block *exec.BlockExecution) (cont bool) {
   102  					if i == gap {
   103  						// Send sync signal
   104  						syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
   105  					}
   106  					if i == gap*2 {
   107  						// Send reload signal (shouldn't dump capture logger)
   108  						syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
   109  					}
   110  					if i == gap*3 {
   111  						return false
   112  					}
   113  					i++
   114  					return true
   115  				}))
   116  			f, err := os.OpenFile(path, os.O_RDONLY, 0644)
   117  			require.NoError(t, err)
   118  			n := 0
   119  			scanner := bufio.NewScanner(f)
   120  			for scanner.Scan() {
   121  				n++
   122  			}
   123  			// We may spill a few writes in ring buffer
   124  			assert.InEpsilon(t, buffer*2, n, 10)
   125  		})
   126  
   127  	})
   128  }
   129  
   130  func bootWaitBlocksShutdown(t testing.TB, validator *acm.PrivateAccount, privateAccounts []*acm.PrivateAccount,
   131  	testConfig *config.BurrowConfig, blockChecker func(block *exec.BlockExecution) (cont bool)) error {
   132  
   133  	kern, err := integration.TestKernel(validator, rpctest.PrivateAccounts, testConfig)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	kern.SetKeyClient(keys.NewLocalKeyClient(keys.NewMemoryKeyStore(privateAccounts...), kern.Logger))
   139  	kern.SetKeyStore(keys.NewFilesystemKeyStore(keys.DefaultKeysDir, false))
   140  	ctx := context.Background()
   141  	if err = kern.Boot(); err != nil {
   142  		return err
   143  	}
   144  
   145  	inputAddress := privateAccounts[0].GetAddress()
   146  	tcli := rpctest.NewTransactClient(t, kern.GRPCListenAddress().String())
   147  
   148  	subID := event.GenSubID()
   149  	ch, err := kern.Emitter.Subscribe(ctx, subID, exec.QueryForBlockExecution(), 10)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	defer kern.Emitter.UnsubscribeAll(ctx, subID)
   154  
   155  	stopCh := make(chan struct{})
   156  	// Catch first error only
   157  	errCh := make(chan error, 1)
   158  	// Generate a few transactions concurrent with restarts
   159  	go func() {
   160  		pow := testConfig.GenesisDoc.Validators[0].Amount
   161  		for {
   162  			// Fire and forget - we can expect a few to fail since we are restarting kernel
   163  			txe, err := tcli.CallTxSync(ctx, &payload.CallTx{
   164  				Input: &payload.TxInput{
   165  					Address: inputAddress,
   166  					Amount:  2,
   167  				},
   168  				Address:  nil,
   169  				Data:     solidity.Bytecode_StrangeLoop,
   170  				Fee:      2,
   171  				GasLimit: 10000,
   172  			})
   173  			handleTxe(txe, err, errCh)
   174  
   175  			txe, err = tcli.BroadcastTxSync(ctx, &rpctransact.TxEnvelopeParam{
   176  				Payload: &payload.Any{
   177  					GovTx: payload.AlterBalanceTx(inputAddress, validator, balance.New().Power(pow)),
   178  				},
   179  			})
   180  			handleTxe(txe, err, errCh)
   181  			select {
   182  			case <-stopCh:
   183  				close(errCh)
   184  				return
   185  			default:
   186  				time.Sleep(time.Millisecond)
   187  			}
   188  			pow += 100
   189  		}
   190  	}()
   191  
   192  	cont := true
   193  	for cont {
   194  		select {
   195  		case <-time.After(2 * time.Second):
   196  			return fmt.Errorf("timed out waiting for block")
   197  		case msg := <-ch:
   198  			if blockChecker == nil {
   199  				cont = false
   200  			} else {
   201  				cont = blockChecker(msg.(*exec.BlockExecution))
   202  			}
   203  		}
   204  	}
   205  
   206  	close(stopCh)
   207  
   208  	for err := range errCh {
   209  		return err
   210  	}
   211  
   212  	return kern.Shutdown(ctx)
   213  }
   214  
   215  func handleTxe(txe *exec.TxExecution, err error, errCh chan<- error) {
   216  	if err == nil {
   217  		err = txe.Exception.AsError()
   218  	}
   219  	if err != nil {
   220  		statusError := status.Convert(err)
   221  		// We expect the GRPC service to be unavailable when we restart
   222  		if statusError != nil && statusError.Code() != codes.Unavailable {
   223  			// Don't block - we'll just capture first error
   224  			select {
   225  			case errCh <- err:
   226  			default:
   227  
   228  			}
   229  		}
   230  	}
   231  }