github.com/koko1123/flow-go-1@v0.29.6/module/epochs/machine_account_test.go (about)

     1  package epochs
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/onflow/cadence"
     7  	"github.com/rs/zerolog"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	sdkcrypto "github.com/onflow/flow-go-sdk/crypto"
    12  	"github.com/koko1123/flow-go-1/model/flow"
    13  	"github.com/koko1123/flow-go-1/utils/unittest"
    14  	"github.com/onflow/flow-go/crypto"
    15  )
    16  
    17  // TestMachineAccountChecking tests that CheckMachineAccount captures critical
    18  // misconfigurations of the machine account correctly.
    19  //
    20  // In these tests, local refers to the machine account from the local file,
    21  // remote refers to the machine account from on-chain.
    22  func TestMachineAccountChecking(t *testing.T) {
    23  	conf := DefaultMachineAccountValidatorConfig()
    24  
    25  	t.Run("consistent machine account", func(t *testing.T) {
    26  		local, remote := unittest.MachineAccountFixture(t)
    27  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    28  		require.NoError(t, err)
    29  	})
    30  	t.Run("inconsistent address", func(t *testing.T) {
    31  		local, remote := unittest.MachineAccountFixture(t)
    32  		remote.Address = unittest.RandomSDKAddressFixture()
    33  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    34  		require.Error(t, err)
    35  	})
    36  	t.Run("inconsistent key", func(t *testing.T) {
    37  		local, remote := unittest.MachineAccountFixture(t)
    38  		randomKey := unittest.PrivateKeyFixture(crypto.ECDSAP256, unittest.DefaultSeedFixtureLength)
    39  		remote.Keys[0].PublicKey = randomKey.PublicKey()
    40  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    41  		require.Error(t, err)
    42  	})
    43  	t.Run("inconsistent hash algo", func(t *testing.T) {
    44  		local, remote := unittest.MachineAccountFixture(t)
    45  		remote.Keys[0].HashAlgo = sdkcrypto.SHA2_384
    46  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    47  		require.Error(t, err)
    48  	})
    49  	t.Run("inconsistent sig algo", func(t *testing.T) {
    50  		local, remote := unittest.MachineAccountFixture(t)
    51  		remote.Keys[0].SigAlgo = sdkcrypto.ECDSA_secp256k1
    52  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    53  		require.Error(t, err)
    54  	})
    55  	t.Run("account without keys", func(t *testing.T) {
    56  		local, remote := unittest.MachineAccountFixture(t)
    57  		remote.Keys = nil
    58  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    59  		require.Error(t, err)
    60  	})
    61  	t.Run("account with insufficient keys", func(t *testing.T) {
    62  		local, remote := unittest.MachineAccountFixture(t)
    63  		// increment key index so it doesn't match remote account
    64  		local.KeyIndex = local.KeyIndex + 1
    65  		err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    66  		require.Error(t, err)
    67  	})
    68  	t.Run("invalid role", func(t *testing.T) {
    69  		local, remote := unittest.MachineAccountFixture(t)
    70  		for _, role := range flow.Roles() {
    71  			// skip valid roles
    72  			if role == flow.RoleCollection || role == flow.RoleConsensus {
    73  				continue
    74  			}
    75  
    76  			err := CheckMachineAccountInfo(zerolog.Nop(), conf, role, local, remote)
    77  			require.Error(t, err)
    78  		}
    79  	})
    80  
    81  	t.Run("account with < hard minimum balance", func(t *testing.T) {
    82  		t.Run("collection", func(t *testing.T) {
    83  			local, remote := unittest.MachineAccountFixture(t)
    84  			remote.Balance = uint64(defaultHardMinBalanceLN) - 1
    85  			err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleCollection, local, remote)
    86  			require.Error(t, err)
    87  		})
    88  		t.Run("consensus", func(t *testing.T) {
    89  			local, remote := unittest.MachineAccountFixture(t)
    90  			remote.Balance = uint64(defaultHardMinBalanceSN) - 1
    91  			err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote)
    92  			require.Error(t, err)
    93  		})
    94  	})
    95  
    96  	t.Run("disable balance checking", func(t *testing.T) {
    97  		minBalance, err := cadence.NewUFix64("0.001")
    98  		require.NoError(t, err)
    99  
   100  		balanceDisabledConfig := DefaultMachineAccountValidatorConfig()
   101  		WithoutBalanceChecks(&balanceDisabledConfig)
   102  
   103  		t.Run("collection", func(t *testing.T) {
   104  			local, remote := unittest.MachineAccountFixture(t)
   105  			remote.Balance = uint64(minBalance)
   106  			err := CheckMachineAccountInfo(zerolog.Nop(), balanceDisabledConfig, flow.RoleCollection, local, remote)
   107  			require.NoError(t, err)
   108  		})
   109  		t.Run("consensus", func(t *testing.T) {
   110  			local, remote := unittest.MachineAccountFixture(t)
   111  			remote.Balance = uint64(minBalance)
   112  			err := CheckMachineAccountInfo(zerolog.Nop(), balanceDisabledConfig, flow.RoleConsensus, local, remote)
   113  			require.NoError(t, err)
   114  		})
   115  	})
   116  
   117  	// should log a warning when balance below soft minimum balance (but not
   118  	// below hard minimum balance)
   119  	t.Run("account with < soft minimum balance", func(t *testing.T) {
   120  		t.Run("collection", func(t *testing.T) {
   121  			local, remote := unittest.MachineAccountFixture(t)
   122  			remote.Balance = uint64(defaultSoftMinBalanceLN) - 1
   123  			log, hook := unittest.HookedLogger()
   124  
   125  			err := CheckMachineAccountInfo(log, conf, flow.RoleCollection, local, remote)
   126  			assert.NoError(t, err)
   127  			assert.Regexp(t, "machine account balance is below recommended balance", hook.Logs())
   128  		})
   129  		t.Run("consensus", func(t *testing.T) {
   130  			local, remote := unittest.MachineAccountFixture(t)
   131  			remote.Balance = uint64(defaultSoftMinBalanceSN) - 1
   132  			log, hook := unittest.HookedLogger()
   133  
   134  			err := CheckMachineAccountInfo(log, conf, flow.RoleConsensus, local, remote)
   135  			assert.NoError(t, err)
   136  			assert.Regexp(t, "machine account balance is below recommended balance", hook.Logs())
   137  		})
   138  	})
   139  
   140  	// should log a warning when the local file deviates from defaults
   141  	t.Run("local file deviates from defaults", func(t *testing.T) {
   142  		t.Run("hash algo", func(t *testing.T) {
   143  			local, remote := unittest.MachineAccountFixture(t)
   144  			local.HashAlgorithm = sdkcrypto.SHA3_384     // non-standard hash algo
   145  			remote.Keys[0].HashAlgo = sdkcrypto.SHA3_384 // consistent between local/remote
   146  			log, hook := unittest.HookedLogger()
   147  
   148  			err := CheckMachineAccountInfo(log, conf, flow.RoleConsensus, local, remote)
   149  			assert.NoError(t, err)
   150  			assert.Regexp(t, "non-standard hash algo", hook.Logs())
   151  		})
   152  		t.Run("sig algo", func(t *testing.T) {
   153  			local, remote := unittest.MachineAccountFixture(t)
   154  
   155  			// non-standard sig algo
   156  			sk := unittest.PrivateKeyFixture(crypto.ECDSASecp256k1, unittest.DefaultSeedFixtureLength)
   157  			local.EncodedPrivateKey = sk.Encode()
   158  			local.SigningAlgorithm = sdkcrypto.ECDSA_secp256k1
   159  			// consistent between local/remote
   160  			remote.Keys[0].PublicKey = sk.PublicKey()
   161  			remote.Keys[0].SigAlgo = sdkcrypto.ECDSA_secp256k1
   162  			log, hook := unittest.HookedLogger()
   163  
   164  			err := CheckMachineAccountInfo(log, conf, flow.RoleConsensus, local, remote)
   165  			assert.NoError(t, err)
   166  			assert.Regexp(t, "non-standard signing algo", hook.Logs())
   167  		})
   168  		t.Run("key index", func(t *testing.T) {
   169  			local, remote := unittest.MachineAccountFixture(t)
   170  			local.KeyIndex = 1                                // non-standard key index
   171  			remote.Keys = append(remote.Keys, remote.Keys[0]) // key with index exists on remote
   172  			remote.Keys[1].Index = 1
   173  			log, hook := unittest.HookedLogger()
   174  
   175  			err := CheckMachineAccountInfo(log, conf, flow.RoleConsensus, local, remote)
   176  			assert.NoError(t, err)
   177  			assert.Regexp(t, "non-standard key index", hook.Logs())
   178  		})
   179  	})
   180  }
   181  
   182  // TestBackoff tests the backoff config behaves as expected. In particular, once
   183  // we reach the cap duration, all future backoffs should be equal to the cap duration.
   184  func TestMachineAccountValidatorBackoff_Overflow(t *testing.T) {
   185  
   186  	backoff := checkMachineAccountRetryBackoff()
   187  
   188  	// once the backoff reaches the maximum, it should remain in [(1-jitter)*max,(1+jitter*max)]
   189  	max := checkMachineAccountRetryMax + checkMachineAccountRetryMax*(checkMachineAccountRetryJitterPct+1)/100
   190  	min := checkMachineAccountRetryMax - checkMachineAccountRetryMax*(checkMachineAccountRetryJitterPct+1)/100
   191  
   192  	lastWait, stop := backoff.Next()
   193  	assert.False(t, stop)
   194  	for i := 0; i < 100; i++ {
   195  		wait, stop := backoff.Next()
   196  		assert.False(t, stop)
   197  		// the backoff value should either:
   198  		// * strictly increase, or
   199  		// * be within range of max duration + jitter
   200  		if wait < lastWait {
   201  			assert.Less(t, min, wait)
   202  			assert.Less(t, wait, max)
   203  		}
   204  		lastWait = wait
   205  	}
   206  }