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 }