github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/epochs/machine_account_test.go (about)

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