diff --git a/config/localTemplate.go b/config/localTemplate.go index 020361918d..8172545575 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -493,6 +493,11 @@ type Local struct { // guarantees in terms of functionality or future support. EnableExperimentalAPI bool `version[26]:"false"` + // DisableLedgerLRUCache disables LRU caches in ledger. + // Setting it to TRUE might result in significant performance degradation + // and SHOULD NOT be used for other reasons than testing. + DisableLedgerLRUCache bool `version[27]:"false"` + // EnableFollowMode launches the node in "follower" mode. This turns off the agreement service, // and APIs related to broadcasting transactions, and enables APIs which can retrieve detailed information // from ledger caches and can control the ledger round. diff --git a/config/local_defaults.go b/config/local_defaults.go index f0e739a2db..48ddae4be9 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -49,6 +49,7 @@ var defaultLocal = Local{ DNSSecurityFlags: 1, DeadlockDetection: 0, DeadlockDetectionThreshold: 30, + DisableLedgerLRUCache: false, DisableLocalhostConnectionRateLimit: true, DisableNetworking: false, DisableOutgoingConnectionThrottling: false, diff --git a/installer/config.json.example b/installer/config.json.example index 52b86764ed..3eefcf69ef 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -28,6 +28,7 @@ "DNSSecurityFlags": 1, "DeadlockDetection": 0, "DeadlockDetectionThreshold": 30, + "DisableLedgerLRUCache": false, "DisableLocalhostConnectionRateLimit": true, "DisableNetworking": false, "DisableOutgoingConnectionThrottling": false, diff --git a/ledger/acctonline.go b/ledger/acctonline.go index ea9c411735..ebc6174187 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -117,12 +117,16 @@ type onlineAccounts struct { // maxAcctLookback sets the minimim deltas size to keep in memory acctLookback uint64 + + // disableCache (de)activates the LRU cache use in onlineAccounts + disableCache bool } // initialize initializes the accountUpdates structure func (ao *onlineAccounts) initialize(cfg config.Local) { ao.accountsReadCond = sync.NewCond(ao.accountsMu.RLocker()) ao.acctLookback = cfg.MaxAcctLookback + ao.disableCache = cfg.DisableLedgerLRUCache } // loadFromDisk is the 2nd level initialization, and is required before the onlineAccounts becomes functional @@ -184,7 +188,11 @@ func (ao *onlineAccounts) initializeFromDisk(l ledgerForTracker, lastBalancesRou ao.accounts = make(map[basics.Address]modifiedOnlineAccount) ao.deltasAccum = []int{0} - ao.baseOnlineAccounts.init(ao.log, baseAccountsPendingAccountsBufferSize, baseAccountsPendingAccountsWarnThreshold) + if !ao.disableCache { + ao.baseOnlineAccounts.init(ao.log, baseAccountsPendingAccountsBufferSize, baseAccountsPendingAccountsWarnThreshold) + } else { + ao.baseOnlineAccounts.init(ao.log, 0, 0) + } return } diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 8e5ec2f752..d3caf1ff60 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -227,6 +227,9 @@ type accountUpdates struct { // maxAcctLookback sets the minimim deltas size to keep in memory acctLookback uint64 + + // disableCache (de)activates the LRU cache use in accountUpdates + disableCache bool } // RoundOffsetError is an error for when requested round is behind earliest stored db entry @@ -296,6 +299,8 @@ func (au *accountUpdates) initialize(cfg config.Local) { // log metrics au.logAccountUpdatesMetrics = cfg.EnableAccountUpdatesStats au.logAccountUpdatesInterval = cfg.AccountUpdatesStatsInterval + + au.disableCache = cfg.DisableLedgerLRUCache } // loadFromDisk is the 2nd level initialization, and is required before the accountUpdates becomes functional @@ -962,9 +967,15 @@ func (au *accountUpdates) initializeFromDisk(l ledgerForTracker, lastBalancesRou au.creatables = make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable) au.deltasAccum = []int{0} - au.baseAccounts.init(au.log, baseAccountsPendingAccountsBufferSize, baseAccountsPendingAccountsWarnThreshold) - au.baseResources.init(au.log, baseResourcesPendingAccountsBufferSize, baseResourcesPendingAccountsWarnThreshold) - au.baseKVs.init(au.log, baseKVPendingBufferSize, baseKVPendingWarnThreshold) + if !au.disableCache { + au.baseAccounts.init(au.log, baseAccountsPendingAccountsBufferSize, baseAccountsPendingAccountsWarnThreshold) + au.baseResources.init(au.log, baseResourcesPendingAccountsBufferSize, baseResourcesPendingAccountsWarnThreshold) + au.baseKVs.init(au.log, baseKVPendingBufferSize, baseKVPendingWarnThreshold) + } else { + au.baseAccounts.init(au.log, 0, 0) + au.baseResources.init(au.log, 0, 0) + au.baseKVs.init(au.log, 0, 0) + } return } diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 7d6c3485c6..66d14f5943 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -475,20 +475,13 @@ func checkOnlineAcctUpdatesConsistency(t *testing.T, ao *onlineAccounts, rnd bas } } -func TestAcctUpdates(t *testing.T) { - partitiontest.PartitionTest(t) - - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - t.Skip("This test is too slow on ARM and causes travis builds to time out") - } - +func testAcctUpdates(t *testing.T, conf config.Local) { // The next operations are heavy on the memory. // Garbage collection helps prevent trashing runtime.GC() proto := config.Consensus[protocol.ConsensusCurrentVersion] - conf := config.GetDefaultLocal() for _, lookback := range []uint64{conf.MaxAcctLookback, proto.MaxBalLookback} { t.Run(fmt.Sprintf("lookback=%d", lookback), func(t *testing.T) { @@ -597,6 +590,13 @@ func TestAcctUpdates(t *testing.T) { } } +func TestAcctUpdates(t *testing.T) { + partitiontest.PartitionTest(t) + + conf := config.GetDefaultLocal() + ledgertesting.WithAndWithoutLRUCache(t, conf, testAcctUpdates) +} + func BenchmarkBalancesChanges(b *testing.B) { if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { b.Skip("This test is too slow on ARM and causes travis builds to time out") @@ -712,20 +712,21 @@ func BenchmarkCalibrateCacheNodeSize(b *testing.B) { // The TestAcctUpdatesUpdatesCorrectness conduct a correctless test for the accounts update in the following way - // Each account is initialized with 100 algos. // On every round, each account move variable amount of funds to an accumulating account. -// The deltas for each accounts are picked by using the lookup method. +// The deltas for each account are picked by using the lookup method. // At the end of the test, we verify that each account has the expected amount of algos. // In addition, throughout the test, we check ( using lookup ) that the historical balances, *beyond* the // lookback are generating either an error, or returning the correct amount. func TestAcctUpdatesUpdatesCorrectness(t *testing.T) { partitiontest.PartitionTest(t) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - t.Skip("This test is too slow on ARM and causes travis builds to time out") - } + cfgLocal := config.GetDefaultLocal() + ledgertesting.WithAndWithoutLRUCache(t, cfgLocal, testAcctUpdatesUpdatesCorrectness) +} +func testAcctUpdatesUpdatesCorrectness(t *testing.T, cfg config.Local) { // create new protocol version, which has lower look back. testProtocolVersion := protocol.ConsensusCurrentVersion - maxAcctLookback := config.GetDefaultLocal().MaxAcctLookback + maxAcctLookback := cfg.MaxAcctLookback inMemory := true testFunction := func(t *testing.T) { @@ -750,8 +751,7 @@ func TestAcctUpdatesUpdatesCorrectness(t *testing.T) { accts[0][addr] = accountData } - conf := config.GetDefaultLocal() - au, _ := newAcctUpdates(t, ml, conf) + au, _ := newAcctUpdates(t, ml, cfg) defer au.close() // cover 10 genesis blocks diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index a08a3b74b2..fa840b40a4 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -42,8 +42,8 @@ func TestPayAction(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // Inner txns start in v30 - ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() ai := dl.fundedApp(addrs[0], 200000, // account min balance, plus fees @@ -159,7 +159,8 @@ func TestAxferAction(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusFuture) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusFuture, cfg) defer l.Close() asa := txntest.Txn{ @@ -1354,8 +1355,8 @@ func TestCreateAndUse(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // At 30 the asset reference is illegal, then from v31 it works. - ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() createapp := txntest.Txn{ @@ -1424,8 +1425,8 @@ func TestGtxnEffects(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // At 30 `gtxn CreatedAssetId is illegal, then from v31 it works. - ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() createapp := txntest.Txn{ @@ -1486,8 +1487,8 @@ func TestBasicReentry(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() app0 := txntest.Txn{ @@ -1680,8 +1681,8 @@ func TestMaxInnerTxForSingleAppCall(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // v31 = inner appl - ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() program := ` @@ -1840,8 +1841,8 @@ func TestInnerAppVersionCalling(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // 31 allowed inner appls. v34 lowered proto.MinInnerApplVersion - ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() three, err := logic.AssembleStringWithVersion("int 1", 3) @@ -2034,8 +2035,8 @@ func TestAppDowngrade(t *testing.T) { // Confirm that in old protocol version, downgrade is legal // Start at 28 because we want to v4 app to downgrade to v3 - ledgertesting.TestConsensusRange(t, 28, 30, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 28, 30, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() create := txntest.Txn{ @@ -2065,8 +2066,8 @@ func TestAppDowngrade(t *testing.T) { dl.fullBlock(&update) }) - ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() create := txntest.Txn{ @@ -2310,7 +2311,8 @@ func executeMegaContract(b *testing.B) { var cv protocol.ConsensusVersion = "temp test" config.Consensus[cv] = vTest - l := newSimpleLedgerWithConsensusVersion(b, genBalances, cv) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerWithConsensusVersion(b, genBalances, cv, cfg) defer l.Close() defer delete(config.Consensus, cv) @@ -2736,7 +2738,8 @@ func TestClearStateInnerPay(t *testing.T) { t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newSimpleLedgerWithConsensusVersion(t, genBalances, test.consensus) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerWithConsensusVersion(t, genBalances, test.consensus, cfg) defer l.Close() app0 := txntest.Txn{ @@ -3056,8 +3059,8 @@ func TestForeignAppAccountsAccessible(t *testing.T) { partitiontest.PartitionTest(t) genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 32, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 32, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() appA := txntest.Txn{ @@ -3122,8 +3125,8 @@ func TestForeignAppAccountsImmutable(t *testing.T) { partitiontest.PartitionTest(t) genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 32, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 32, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() appA := txntest.Txn{ @@ -3176,8 +3179,8 @@ func TestForeignAppAccountsMutable(t *testing.T) { partitiontest.PartitionTest(t) genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 32, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 32, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() appA := txntest.Txn{ @@ -3257,8 +3260,8 @@ func TestReloadWithTxns(t *testing.T) { partitiontest.PartitionTest(t) genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 34, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 34, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() dl.fullBlock() // So that the `block` opcode has a block to inspect @@ -3286,8 +3289,8 @@ func TestEvalAppState(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // v24 = apps - ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() appcall1 := txntest.Txn{ @@ -3338,8 +3341,8 @@ func TestGarbageClearState(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // v24 = apps - ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() createTxn := txntest.Txn{ @@ -3362,8 +3365,8 @@ func TestRewardsInAD(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // v15 put rewards into ApplyData - ledgertesting.TestConsensusRange(t, 11, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 11, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() payTxn := txntest.Txn{Type: protocol.PaymentTx, Sender: addrs[0], Receiver: addrs[1]} @@ -3411,8 +3414,8 @@ func TestDeleteNonExistantKeys(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // AVM v2 (apps) - ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() const appID basics.AppIndex = 1 @@ -3452,8 +3455,8 @@ func TestDuplicates(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 11, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 11, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() pay := txntest.Txn{ @@ -3488,8 +3491,8 @@ func TestHeaderAccess(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // Added in v34 - ledgertesting.TestConsensusRange(t, 34, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 34, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() fvt := txntest.Txn{ @@ -3538,8 +3541,8 @@ func TestLogsInBlock(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() // Run tests from v30 onward - ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() createTxn := txntest.Txn{ @@ -3598,8 +3601,8 @@ func TestUnfundedSenders(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() asaIndex := basics.AssetIndex(1) @@ -3711,8 +3714,8 @@ func TestAppCallAppDuringInit(t *testing.T) { partitiontest.PartitionTest(t) genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() approve := txntest.Txn{ diff --git a/ledger/archival_test.go b/ledger/archival_test.go index cf96cb05c3..d8783d9882 100644 --- a/ledger/archival_test.go +++ b/ledger/archival_test.go @@ -130,14 +130,6 @@ func TestArchival(t *testing.T) { // // We generate mostly empty blocks, with the exception of timestamps, // which affect participationTracker.committedUpTo()'s return value. - - // This test was causing random crashes on travis when executed with the race detector - // due to memory exhustion. For the time being, I'm taking it offline from the ubuntu - // configuration where it used to cause failuires. - if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" { - t.Skip("Skipping the TestArchival as it tend to randomally fail on travis linux-amd64") - } - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() const inMem = true diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index f9099a4f91..47291575bf 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -138,8 +138,8 @@ func TestBoxCreate(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() // increment for a size 24 box with 4 letter name @@ -210,8 +210,8 @@ func TestBoxRecreate(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() // increment for a size 4 box with 4 letter name @@ -261,14 +261,14 @@ func TestBoxCreateAvailability(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() accessInCreate := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: 0, // This is a create + ApplicationID: 0, // This is an app-creation Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("hello")}}, ApprovalProgram: ` byte "hello" @@ -366,9 +366,9 @@ func TestBoxRW(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { t.Parallel() - dl := NewDoubleLedger(t, genBalances, cv) + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() var bufNewLogger bytes.Buffer @@ -441,8 +441,8 @@ func TestBoxAccountData(t *testing.T) { } genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() proto := config.Consensus[cv] @@ -529,8 +529,8 @@ func TestBoxIOBudgets(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() appIndex := dl.fundedApp(addrs[0], 0, boxAppSource) @@ -593,8 +593,8 @@ func TestBoxInners(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion) { - dl := NewDoubleLedger(t, genBalances, cv) + ledgertesting.TestConsensusRange(t, boxVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() // Advance the creatable counter, so we don't have very low app ids that diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index b85a380f1b..e442c4030c 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -378,8 +378,9 @@ func TestReproducibleCatchpointLabels(t *testing.T) { partitiontest.PartitionTest(t) if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - t.Skip("This test is too slow on ARM and causes travis builds to time out") + t.Skip("This test is too slow on ARM and causes CI builds to time out") } + // create new protocol version, which has lower lookback testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestReproducibleCatchpointLabels") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] @@ -1474,8 +1475,9 @@ func TestCatchpointFastUpdates(t *testing.T) { partitiontest.PartitionTest(t) if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - t.Skip("This test is too slow on ARM and causes travis builds to time out") + t.Skip("This test is too slow on ARM and causes CI builds to time out") } + proto := config.Consensus[protocol.ConsensusCurrentVersion] accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} diff --git a/ledger/catchpointwriter_test.go b/ledger/catchpointwriter_test.go index 68d5854380..37f76a15c4 100644 --- a/ledger/catchpointwriter_test.go +++ b/ledger/catchpointwriter_test.go @@ -660,7 +660,8 @@ func TestExactAccountChunk(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - dl := NewDoubleLedger(t, genBalances, protocol.ConsensusFuture) + cfg := config.GetDefaultLocal() + dl := NewDoubleLedger(t, genBalances, protocol.ConsensusFuture, cfg) defer dl.Close() pay := txntest.Txn{ @@ -704,7 +705,8 @@ func TestCatchpointAfterTxns(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - dl := NewDoubleLedger(t, genBalances, protocol.ConsensusFuture) + cfg := config.GetDefaultLocal() + dl := NewDoubleLedger(t, genBalances, protocol.ConsensusFuture, cfg) defer dl.Close() boxApp := dl.fundedApp(addrs[1], 1_000_000, boxAppSource) @@ -807,7 +809,8 @@ func TestCatchpointAfterBoxTxns(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - dl := NewDoubleLedger(t, genBalances, protocol.ConsensusFuture) + cfg := config.GetDefaultLocal() + dl := NewDoubleLedger(t, genBalances, protocol.ConsensusFuture, cfg) defer dl.Close() boxApp := dl.fundedApp(addrs[1], 1_000_000, boxAppSource) diff --git a/ledger/double_test.go b/ledger/double_test.go index 9b4ca20f11..bbc5e95206 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -19,6 +19,7 @@ package ledger import ( "testing" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" @@ -55,9 +56,9 @@ func (dl DoubleLedger) Close() { } // NewDoubleLedger creates a new DoubleLedger with the supplied balances and consensus version. -func NewDoubleLedger(t *testing.T, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion) DoubleLedger { - g := newSimpleLedgerWithConsensusVersion(t, balances, cv) - v := newSimpleLedgerFull(t, balances, cv, g.GenesisHash()) +func NewDoubleLedger(t *testing.T, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local) DoubleLedger { + g := newSimpleLedgerWithConsensusVersion(t, balances, cv, cfg) + v := newSimpleLedgerFull(t, balances, cv, g.GenesisHash(), cfg) return DoubleLedger{t, g, v, nil} } diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index 4a2317dc1a..c5c9354520 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -318,7 +318,8 @@ func TestRekeying(t *testing.T) { func testEvalAppPoolingGroup(t *testing.T, schema basics.StateSchema, approvalProgram string, consensusVersion protocol.ConsensusVersion) error { genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newSimpleLedgerWithConsensusVersion(t, genBalances, consensusVersion) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerWithConsensusVersion(t, genBalances, consensusVersion, cfg) defer l.Close() eval := nextBlock(t, l) @@ -403,7 +404,8 @@ func TestMinBalanceChanges(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusCurrentVersion) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusCurrentVersion, cfg) defer l.Close() createTxn := txntest.Txn{ @@ -481,7 +483,8 @@ func TestAppInsMinBalance(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusV30) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusV30, cfg) defer l.Close() const appid basics.AppIndex = 1 diff --git a/ledger/ledger.go b/ledger/ledger.go index bf3e6f0a74..1064ad54f3 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -376,7 +376,7 @@ func initBlocksDB(tx *sql.Tx, l *Ledger, initBlocks []bookkeeping.Block, isArchi // Close reclaims resources used by the ledger (namely, the database connection // and goroutines used by trackers). func (l *Ledger) Close() { - // we shut the the blockqueue first, since it's sync goroutine dispatches calls + // we shut the blockqueue first, since it's sync goroutine dispatches calls // back to the trackers. if l.blockQ != nil { l.blockQ.stop() diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 6a2ca25e65..9a001e7ecd 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -197,19 +197,23 @@ func (l *Ledger) addBlockTxns(t *testing.T, accounts map[basics.Address]basics.A return l.AddBlock(blk, agreement.Certificate{}) } -func TestLedgerBasic(t *testing.T) { - partitiontest.PartitionTest(t) - +func testLedgerBasic(t *testing.T, cfg config.Local) { genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) const inMem = true - cfg := config.GetDefaultLocal() - cfg.Archival = true log := logging.TestingLog(t) l, err := OpenLedger(log, t.Name(), inMem, genesisInitState, cfg) require.NoError(t, err, "could not open ledger") defer l.Close() } +func TestLedgerBasic(t *testing.T) { + partitiontest.PartitionTest(t) + cfg := config.GetDefaultLocal() + cfg.Archival = true + + ledgertesting.WithAndWithoutLRUCache(t, cfg, testLedgerBasic) +} + func TestLedgerBlockHeaders(t *testing.T) { partitiontest.PartitionTest(t) @@ -1503,14 +1507,10 @@ func triggerTrackerFlush(t *testing.T, l *Ledger, genesisInitState ledgercore.In l.trackers.waitAccountsWriting() } -func TestLedgerReload(t *testing.T) { - partitiontest.PartitionTest(t) - +func testLedgerReload(t *testing.T, cfg config.Local) { dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() const inMem = true - cfg := config.GetDefaultLocal() - cfg.Archival = true log := logging.TestingLog(t) log.SetLevel(logging.Info) l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) @@ -1539,6 +1539,13 @@ func TestLedgerReload(t *testing.T) { } } +func TestLedgerReload(t *testing.T) { + partitiontest.PartitionTest(t) + cfg := config.GetDefaultLocal() + cfg.Archival = true + ledgertesting.WithAndWithoutLRUCache(t, cfg, testLedgerReload) +} + func TestWaitLedgerReload(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -2883,17 +2890,13 @@ func verifyVotersContent(t *testing.T, expected map[basics.Round]*ledgercore.Vot } } -func TestVotersReloadFromDisk(t *testing.T) { - partitiontest.PartitionTest(t) - +func testVotersReloadFromDisk(t *testing.T, cfg config.Local) { proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true - cfg := config.GetDefaultLocal() - cfg.Archival = false - cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) log.SetLevel(logging.Info) l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) @@ -2931,17 +2934,26 @@ func TestVotersReloadFromDisk(t *testing.T) { verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) } -func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { +func TestVotersReloadFromDisk(t *testing.T) { partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + + ledgertesting.WithAndWithoutLRUCache(t, cfg, testVotersReloadFromDisk) +} + +func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg config.Local) { proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true - cfg := config.GetDefaultLocal() - cfg.Archival = false - cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) log.SetLevel(logging.Info) l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) @@ -2991,17 +3003,25 @@ func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) } -func TestVotersReloadFromDiskPassRecoveryPeriod(t *testing.T) { +func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + + ledgertesting.WithAndWithoutLRUCache(t, cfg, testVotersReloadFromDiskAfterOneStateProofCommitted) +} + +func testVotersReloadFromDiskPassRecoveryPeriod(t *testing.T, cfg config.Local) { + proto := config.Consensus[protocol.ConsensusCurrentVersion] + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true - cfg := config.GetDefaultLocal() - cfg.Archival = false - cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) log.SetLevel(logging.Info) l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) @@ -3052,3 +3072,15 @@ func TestVotersReloadFromDiskPassRecoveryPeriod(t *testing.T) { require.False(t, found) require.Equal(t, beforeRemoveVotersLen, len(l.acctsOnline.voters.votersForRoundCache)) } + +func TestVotersReloadFromDiskPassRecoveryPeriod(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + + ledgertesting.WithAndWithoutLRUCache(t, cfg, testVotersReloadFromDiskPassRecoveryPeriod) +} diff --git a/ledger/lruaccts.go b/ledger/lruaccts.go index 22928012af..a6962cab81 100644 --- a/ledger/lruaccts.go +++ b/ledger/lruaccts.go @@ -23,22 +23,25 @@ import ( ) // lruAccounts provides a storage class for the most recently used accounts data. -// It doesn't have any synchronization primitive on it's own and require to be -// syncronized by the caller. +// It doesn't have any synchronization primitive on its own and require to be +// synchronized by the caller. type lruAccounts struct { // accountsList contain the list of persistedAccountData, where the front ones are the most "fresh" // and the ones on the back are the oldest. accountsList *persistedAccountDataList // accounts provides fast access to the various elements in the list by using the account address + // if lruAccounts is set with pendingWrites 0, then accounts is nil accounts map[basics.Address]*persistedAccountDataListNode // pendingAccounts are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these, // it would call flushPendingWrites and these would be merged into the accounts/accountsList + // if lruAccounts is set with pendingWrites 0, then pendingAccounts is nil pendingAccounts chan store.PersistedAccountData // log interface; used for logging the threshold event. log logging.Logger // pendingWritesWarnThreshold is the threshold beyond we would write a warning for exceeding the number of pendingAccounts entries pendingWritesWarnThreshold int + // if lruAccounts is set with pendingWrites 0, then pendingNotFound and notFound is nil pendingNotFound chan basics.Address notFound map[basics.Address]struct{} } @@ -46,11 +49,13 @@ type lruAccounts struct { // init initializes the lruAccounts for use. // thread locking semantics : write lock func (m *lruAccounts) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) { - m.accountsList = newPersistedAccountList().allocateFreeNodes(pendingWrites) - m.accounts = make(map[basics.Address]*persistedAccountDataListNode, pendingWrites) - m.pendingAccounts = make(chan store.PersistedAccountData, pendingWrites) - m.notFound = make(map[basics.Address]struct{}, pendingWrites) - m.pendingNotFound = make(chan basics.Address, pendingWrites) + if pendingWrites > 0 { + m.accountsList = newPersistedAccountList().allocateFreeNodes(pendingWrites) + m.accounts = make(map[basics.Address]*persistedAccountDataListNode, pendingWrites) + m.pendingAccounts = make(chan store.PersistedAccountData, pendingWrites) + m.notFound = make(map[basics.Address]struct{}, pendingWrites) + m.pendingNotFound = make(chan basics.Address, pendingWrites) + } m.log = log m.pendingWritesWarnThreshold = pendingWritesWarnThreshold } @@ -127,6 +132,9 @@ func (m *lruAccounts) writeNotFoundPending(addr basics.Address) { // to be promoted to the front of the list. // thread locking semantics : write lock func (m *lruAccounts) write(acctData store.PersistedAccountData) { + if m.accounts == nil { + return + } if el := m.accounts[acctData.Addr]; el != nil { // already exists; is it a newer ? if el.Value.Before(&acctData) { @@ -144,6 +152,9 @@ func (m *lruAccounts) write(acctData store.PersistedAccountData) { // recently used entries. // thread locking semantics : write lock func (m *lruAccounts) prune(newSize int) (removed int) { + if m.accounts == nil { + return + } for { if len(m.accounts) <= newSize { break diff --git a/ledger/lruaccts_test.go b/ledger/lruaccts_test.go index 625d40f3ea..d5f8fdcab9 100644 --- a/ledger/lruaccts_test.go +++ b/ledger/lruaccts_test.go @@ -88,6 +88,42 @@ func TestLRUBasicAccounts(t *testing.T) { } } +func TestLRUAccountsDisable(t *testing.T) { + partitiontest.PartitionTest(t) + + var baseAcct lruAccounts + baseAcct.init(logging.TestingLog(t), 0, 1) + + accountsNum := 5 + + for i := 0; i < accountsNum; i++ { + go func(i int) { + time.Sleep(time.Duration((crypto.RandUint64() % 50)) * time.Millisecond) + acct := store.PersistedAccountData{ + Addr: basics.Address(crypto.Hash([]byte{byte(i)})), + Round: basics.Round(i), + Rowid: int64(i), + AccountData: store.BaseAccountData{MicroAlgos: basics.MicroAlgos{Raw: uint64(i)}}, + } + baseAcct.writePending(acct) + }(i) + } + require.Empty(t, baseAcct.pendingAccounts) + baseAcct.flushPendingWrites() + require.Empty(t, baseAcct.accounts) + + for i := 0; i < accountsNum; i++ { + acct := store.PersistedAccountData{ + Addr: basics.Address(crypto.Hash([]byte{byte(i)})), + Round: basics.Round(i), + Rowid: int64(i), + AccountData: store.BaseAccountData{MicroAlgos: basics.MicroAlgos{Raw: uint64(i)}}, + } + baseAcct.write(acct) + } + require.Empty(t, baseAcct.accounts) +} + func TestLRUAccountsPendingWrites(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/lrukv.go b/ledger/lrukv.go index 77986eb1b6..420f87f5dc 100644 --- a/ledger/lrukv.go +++ b/ledger/lrukv.go @@ -30,18 +30,20 @@ type cachedKVData struct { } // lruKV provides a storage class for the most recently used kv data. -// It doesn't have any synchronization primitive on it's own and require to be -// syncronized by the caller. +// It doesn't have any synchronization primitive on its own and require to be +// synchronized by the caller. type lruKV struct { // kvList contain the list of persistedKVData, where the front ones are the most "fresh" // and the ones on the back are the oldest. kvList *persistedKVDataList // kvs provides fast access to the various elements in the list by using the key + // if lruKV is set with pendingWrites 0, then kvs is nil kvs map[string]*persistedKVDataListNode // pendingKVs are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these, // it would call flushPendingWrites and these would be merged into the kvs/kvList + // if lruKV is set with pendingWrites 0, then pendingKVs is nil pendingKVs chan cachedKVData // log interface; used for logging the threshold event. @@ -54,9 +56,11 @@ type lruKV struct { // init initializes the lruKV for use. // thread locking semantics : write lock func (m *lruKV) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) { - m.kvList = newPersistedKVList().allocateFreeNodes(pendingWrites) - m.kvs = make(map[string]*persistedKVDataListNode, pendingWrites) - m.pendingKVs = make(chan cachedKVData, pendingWrites) + if pendingWrites > 0 { + m.kvList = newPersistedKVList().allocateFreeNodes(pendingWrites) + m.kvs = make(map[string]*persistedKVDataListNode, pendingWrites) + m.pendingKVs = make(chan cachedKVData, pendingWrites) + } m.log = log m.pendingWritesWarnThreshold = pendingWritesWarnThreshold } @@ -103,6 +107,9 @@ func (m *lruKV) writePending(kv store.PersistedKVData, key string) { // to be promoted to the front of the list. // thread locking semantics : write lock func (m *lruKV) write(kvData store.PersistedKVData, key string) { + if m.kvs == nil { + return + } if el := m.kvs[key]; el != nil { // already exists; is it a newer ? if el.Value.Before(&kvData) { @@ -120,6 +127,9 @@ func (m *lruKV) write(kvData store.PersistedKVData, key string) { // recently used entries. // thread locking semantics : write lock func (m *lruKV) prune(newSize int) (removed int) { + if m.kvs == nil { + return + } for { if len(m.kvs) <= newSize { break diff --git a/ledger/lrukv_test.go b/ledger/lrukv_test.go index 18d0a47072..ce0eb02c03 100644 --- a/ledger/lrukv_test.go +++ b/ledger/lrukv_test.go @@ -80,6 +80,41 @@ func TestLRUBasicKV(t *testing.T) { } } +func TestLRUKVDisable(t *testing.T) { + partitiontest.PartitionTest(t) + + var baseKV lruKV + baseKV.init(logging.TestingLog(t), 0, 1) + + kvNum := 5 + + for i := 1; i <= kvNum; i++ { + go func(i int) { + time.Sleep(time.Duration((crypto.RandUint64() % 50)) * time.Millisecond) + kvValue := fmt.Sprintf("kv %d value", i) + kv := store.PersistedKVData{ + Value: []byte(kvValue), + Round: basics.Round(i), + } + baseKV.writePending(kv, fmt.Sprintf("key%d", i)) + }(i) + } + require.Empty(t, baseKV.pendingKVs) + baseKV.flushPendingWrites() + require.Empty(t, baseKV.kvs) + + for i := 0; i < kvNum; i++ { + kvValue := fmt.Sprintf("kv %d value", i) + kv := store.PersistedKVData{ + Value: []byte(kvValue), + Round: basics.Round(i), + } + baseKV.write(kv, fmt.Sprintf("key%d", i)) + } + + require.Empty(t, baseKV.kvs) +} + func TestLRUKVPendingWrites(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/lruonlineaccts.go b/ledger/lruonlineaccts.go index 35c8224d41..cc8917bf57 100644 --- a/ledger/lruonlineaccts.go +++ b/ledger/lruonlineaccts.go @@ -30,9 +30,11 @@ type lruOnlineAccounts struct { // and the ones on the back are the oldest. accountsList *persistedOnlineAccountDataList // accounts provides fast access to the various elements in the list by using the account address + // if lruOnlineAccounts is set with pendingWrites 0, then accounts is nil accounts map[basics.Address]*persistedOnlineAccountDataListNode // pendingAccounts are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these, // it would call flushPendingWrites and these would be merged into the accounts/accountsList + // if lruOnlineAccounts is set with pendingWrites 0, then pendingAccounts is nil pendingAccounts chan store.PersistedOnlineAccountData // log interface; used for logging the threshold event. log logging.Logger @@ -43,9 +45,11 @@ type lruOnlineAccounts struct { // init initializes the lruAccounts for use. // thread locking semantics : write lock func (m *lruOnlineAccounts) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) { - m.accountsList = newPersistedOnlineAccountList().allocateFreeNodes(pendingWrites) - m.accounts = make(map[basics.Address]*persistedOnlineAccountDataListNode, pendingWrites) - m.pendingAccounts = make(chan store.PersistedOnlineAccountData, pendingWrites) + if pendingWrites > 0 { + m.accountsList = newPersistedOnlineAccountList().allocateFreeNodes(pendingWrites) + m.accounts = make(map[basics.Address]*persistedOnlineAccountDataListNode, pendingWrites) + m.pendingAccounts = make(chan store.PersistedOnlineAccountData, pendingWrites) + } m.log = log m.pendingWritesWarnThreshold = pendingWritesWarnThreshold } @@ -92,6 +96,9 @@ func (m *lruOnlineAccounts) writePending(acct store.PersistedOnlineAccountData) // to be promoted to the front of the list. // thread locking semantics : write lock func (m *lruOnlineAccounts) write(acctData store.PersistedOnlineAccountData) { + if m.accounts == nil { + return + } if el := m.accounts[acctData.Addr]; el != nil { // already exists; is it a newer ? if el.Value.Before(&acctData) { @@ -109,6 +116,9 @@ func (m *lruOnlineAccounts) write(acctData store.PersistedOnlineAccountData) { // recently used entries. // thread locking semantics : write lock func (m *lruOnlineAccounts) prune(newSize int) (removed int) { + if m.accounts == nil { + return + } for { if len(m.accounts) <= newSize { break diff --git a/ledger/lruonlineaccts_test.go b/ledger/lruonlineaccts_test.go index 0e0b314385..84acdb684e 100644 --- a/ledger/lruonlineaccts_test.go +++ b/ledger/lruonlineaccts_test.go @@ -87,6 +87,42 @@ func TestLRUOnlineAccountsBasic(t *testing.T) { } } +func TestLRUOnlineAccountsDisable(t *testing.T) { + partitiontest.PartitionTest(t) + + var baseOnlineAcct lruOnlineAccounts + baseOnlineAcct.init(logging.TestingLog(t), 0, 1) + + accountsNum := 5 + + for i := 0; i < accountsNum; i++ { + go func(i int) { + time.Sleep(time.Duration((crypto.RandUint64() % 50)) * time.Millisecond) + acct := store.PersistedOnlineAccountData{ + Addr: basics.Address(crypto.Hash([]byte{byte(i)})), + Round: basics.Round(i), + Rowid: int64(i), + AccountData: store.BaseOnlineAccountData{MicroAlgos: basics.MicroAlgos{Raw: uint64(i)}}, + } + baseOnlineAcct.writePending(acct) + }(i) + } + require.Empty(t, baseOnlineAcct.pendingAccounts) + baseOnlineAcct.flushPendingWrites() + require.Empty(t, baseOnlineAcct.accounts) + + for i := 0; i < accountsNum; i++ { + acct := store.PersistedOnlineAccountData{ + Addr: basics.Address(crypto.Hash([]byte{byte(i)})), + Round: basics.Round(i), + Rowid: int64(i), + AccountData: store.BaseOnlineAccountData{MicroAlgos: basics.MicroAlgos{Raw: uint64(i)}}, + } + baseOnlineAcct.write(acct) + } + require.Empty(t, baseOnlineAcct.accounts) +} + func TestLRUOnlineAccountsPendingWrites(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/lruresources.go b/ledger/lruresources.go index 222c1d6c07..779ca3cb95 100644 --- a/ledger/lruresources.go +++ b/ledger/lruresources.go @@ -29,19 +29,21 @@ type cachedResourceData struct { address basics.Address } -// lruResources provides a storage class for the most recently used resources data. -// It doesn't have any synchronization primitive on it's own and require to be -// syncronized by the caller. +// lruResources provides a storage class for the most recently used resources' data. +// It doesn't have any synchronization primitive on its own and require to be +// synchronized by the caller. type lruResources struct { // resourcesList contain the list of persistedResourceData, where the front ones are the most "fresh" // and the ones on the back are the oldest. resourcesList *persistedResourcesDataList // resources provides fast access to the various elements in the list by using the account address + // if lruResources is set with pendingWrites 0, then resources is nil resources map[accountCreatable]*persistedResourcesDataListNode // pendingResources are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these, // it would call flushPendingWrites and these would be merged into the resources/resourcesList + // if lruResources is set with pendingWrites 0, then pendingResources is nil pendingResources chan cachedResourceData // log interface; used for logging the threshold event. @@ -50,6 +52,7 @@ type lruResources struct { // pendingWritesWarnThreshold is the threshold beyond we would write a warning for exceeding the number of pendingResources entries pendingWritesWarnThreshold int + // if lruResources is set with pendingWrites 0, then pendingNotFound and notFound is nil pendingNotFound chan accountCreatable notFound map[accountCreatable]struct{} } @@ -57,11 +60,13 @@ type lruResources struct { // init initializes the lruResources for use. // thread locking semantics : write lock func (m *lruResources) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) { - m.resourcesList = newPersistedResourcesList().allocateFreeNodes(pendingWrites) - m.resources = make(map[accountCreatable]*persistedResourcesDataListNode, pendingWrites) - m.pendingResources = make(chan cachedResourceData, pendingWrites) - m.notFound = make(map[accountCreatable]struct{}, pendingWrites) - m.pendingNotFound = make(chan accountCreatable, pendingWrites) + if pendingWrites > 0 { + m.resourcesList = newPersistedResourcesList().allocateFreeNodes(pendingWrites) + m.resources = make(map[accountCreatable]*persistedResourcesDataListNode, pendingWrites) + m.pendingResources = make(chan cachedResourceData, pendingWrites) + m.notFound = make(map[accountCreatable]struct{}, pendingWrites) + m.pendingNotFound = make(chan accountCreatable, pendingWrites) + } m.log = log m.pendingWritesWarnThreshold = pendingWritesWarnThreshold } @@ -149,6 +154,9 @@ func (m *lruResources) writeNotFoundPending(addr basics.Address, idx basics.Crea // to be promoted to the front of the list. // thread locking semantics : write lock func (m *lruResources) write(resData store.PersistedResourcesData, addr basics.Address) { + if m.resources == nil { + return + } if el := m.resources[accountCreatable{address: addr, index: resData.Aidx}]; el != nil { // already exists; is it a newer ? if el.Value.Before(&resData) { @@ -166,6 +174,9 @@ func (m *lruResources) write(resData store.PersistedResourcesData, addr basics.A // recently used entries. // thread locking semantics : write lock func (m *lruResources) prune(newSize int) (removed int) { + if m.resources == nil { + return + } for { if len(m.resources) <= newSize { break diff --git a/ledger/lruresources_test.go b/ledger/lruresources_test.go index 6c97309031..8424fe2fe3 100644 --- a/ledger/lruresources_test.go +++ b/ledger/lruresources_test.go @@ -89,6 +89,48 @@ func TestLRUBasicResources(t *testing.T) { } } +func TestLRUResourcesDisable(t *testing.T) { + partitiontest.PartitionTest(t) + + var baseRes lruResources + baseRes.init(logging.TestingLog(t), 0, 1) + + resourceNum := 5 + + for i := 1; i <= resourceNum; i++ { + go func(i int) { + time.Sleep(time.Duration((crypto.RandUint64() % 50)) * time.Millisecond) + addr := basics.Address(crypto.Hash([]byte{byte(i)})) + res := store.PersistedResourcesData{ + Addrid: int64(i), + Aidx: basics.CreatableIndex(i), + Round: basics.Round(i), + Data: store.ResourcesData{Total: uint64(i)}, + } + baseRes.writePending(res, addr) + baseRes.writeNotFoundPending(addr, basics.CreatableIndex(i)) + }(i) + } + require.Empty(t, baseRes.pendingResources) + require.Empty(t, baseRes.pendingNotFound) + baseRes.flushPendingWrites() + require.Empty(t, baseRes.resources) + require.Empty(t, baseRes.notFound) + + for i := 0; i < resourceNum; i++ { + addr := basics.Address(crypto.Hash([]byte{byte(i)})) + res := store.PersistedResourcesData{ + Addrid: int64(i), + Aidx: basics.CreatableIndex(i), + Round: basics.Round(i), + Data: store.ResourcesData{Total: uint64(i)}, + } + baseRes.write(res, addr) + } + + require.Empty(t, baseRes.resources) +} + func TestLRUResourcesPendingWrites(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/simple_test.go b/ledger/simple_test.go index e934e3f013..d4e44f0c3f 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -35,23 +35,18 @@ import ( "github.com/stretchr/testify/require" ) -func newSimpleLedger(t testing.TB, balances bookkeeping.GenesisBalances) *Ledger { - return newSimpleLedgerWithConsensusVersion(t, balances, protocol.ConsensusFuture) -} - -func newSimpleLedgerWithConsensusVersion(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion) *Ledger { +func newSimpleLedgerWithConsensusVersion(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local) *Ledger { var genHash crypto.Digest crypto.RandBytes(genHash[:]) - return newSimpleLedgerFull(t, balances, cv, genHash) + return newSimpleLedgerFull(t, balances, cv, genHash, cfg) } -func newSimpleLedgerFull(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, genHash crypto.Digest) *Ledger { +func newSimpleLedgerFull(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, genHash crypto.Digest, cfg config.Local) *Ledger { genBlock, err := bookkeeping.MakeGenesisBlock(cv, balances, "test", genHash) require.NoError(t, err) require.False(t, genBlock.FeeSink.IsZero()) require.False(t, genBlock.RewardsPool.IsZero()) dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - cfg := config.GetDefaultLocal() cfg.Archival = true l, err := OpenLedger(logging.Base(), dbName, true, ledgercore.InitState{ Block: genBlock, diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index e96bcc7280..02ae83fce9 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -17,9 +17,11 @@ package testing import ( + "crypto/rand" "fmt" "testing" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" "github.com/stretchr/testify/require" ) @@ -59,6 +61,25 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusFuture, } +func versionStringFromIndex(index int) string { + var version string + if index == len(consensusByNumber)-1 { + version = "vFuture" + } else { + version = fmt.Sprintf("v%d", index) + } + return version +} + +// randBool samples randomness for TestConsensusRange, +// which tests with or without LRU Cache in ledger +func randBool(t *testing.T) bool { + var byteBuffer [1]byte + _, err := rand.Read(byteBuffer[:]) + require.NoError(t, err) + return byteBuffer[0]%2 == 0 +} + // TestConsensusRange allows for running tests against a range of consensus // versions. Generally `start` will be the version that introduced the feature, // and `stop` will be 0 to indicate it should work right on up through vFuture. @@ -69,20 +90,18 @@ var consensusByNumber = []protocol.ConsensusVersion{ // created and inserted in consensusByNumber. At that point, your feature is // probably active in that version. (If it's being held in vFuture, just // increment your `start`.) -func TestConsensusRange(t *testing.T, start, stop int, test func(t *testing.T, ver int, cv protocol.ConsensusVersion)) { +func TestConsensusRange(t *testing.T, start, stop int, test func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local)) { if stop == 0 { // Treat 0 as "future" stop = len(consensusByNumber) - 1 } require.LessOrEqual(t, start, stop) + cfg := config.GetDefaultLocal() for i := start; i <= stop; i++ { - var version string - if i == len(consensusByNumber)-1 { - version = "vFuture" - } else { - version = fmt.Sprintf("v%d", i) - } - t.Run(fmt.Sprintf("cv=%s", version), func(t *testing.T) { - test(t, i, consensusByNumber[i]) + version := versionStringFromIndex(i) + disable := randBool(t) + t.Run(fmt.Sprintf("cv=%s,LRU-cache-disable=%t", version, disable), func(t *testing.T) { + cfg.DisableLedgerLRUCache = disable + test(t, i, consensusByNumber[i], cfg) }) } } @@ -93,12 +112,7 @@ func BenchConsensusRange(b *testing.B, start, stop int, bench func(t *testing.B, stop = len(consensusByNumber) - 1 } for i := start; i <= stop; i++ { - var version string - if i == len(consensusByNumber)-1 { - version = "vFuture" - } else { - version = fmt.Sprintf("v%d", i) - } + version := versionStringFromIndex(i) b.Run(fmt.Sprintf("cv=%s", version), func(b *testing.B) { bench(b, i, consensusByNumber[i]) }) diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index 325373a396..cd5baaa817 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -55,4 +55,7 @@ func TestReleasedVersion(t *testing.T) { require.NotZero(t, params) // just making sure an empty one didn't get put in } + require.Equal(t, versionStringFromIndex(len(consensusByNumber)-1), "vFuture") + require.Equal(t, versionStringFromIndex(36), "v36") + } diff --git a/ledger/testing/withAndWithoutCache.go b/ledger/testing/withAndWithoutCache.go new file mode 100644 index 0000000000..0bb1e75098 --- /dev/null +++ b/ledger/testing/withAndWithoutCache.go @@ -0,0 +1,35 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package testing + +import ( + "testing" + + "github.com/algorand/go-algorand/config" +) + +// WithAndWithoutLRUCache allows for running a test with ledger LRU cache activated and deactivated. +func WithAndWithoutLRUCache(t *testing.T, cfg config.Local, test func(t *testing.T, cfg config.Local)) { + cfg.DisableLedgerLRUCache = false + t.Run("test with lru cache", func(t *testing.T) { + test(t, cfg) + }) + cfg.DisableLedgerLRUCache = true + t.Run("test without lru cache", func(t *testing.T) { + test(t, cfg) + }) +} diff --git a/ledger/txnbench_test.go b/ledger/txnbench_test.go index ddc7aeba99..f2b4b8aa4a 100644 --- a/ledger/txnbench_test.go +++ b/ledger/txnbench_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" @@ -35,7 +36,7 @@ import ( func BenchmarkTxnTypes(b *testing.B) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() ledgertesting.BenchConsensusRange(b, 30, 0, func(b *testing.B, ver int, cv protocol.ConsensusVersion) { - l := newSimpleLedgerWithConsensusVersion(b, genBalances, cv) + l := newSimpleLedgerWithConsensusVersion(b, genBalances, cv, config.GetDefaultLocal()) defer l.Close() createasa := txntest.Txn{ diff --git a/test/testdata/configs/config-v27.json b/test/testdata/configs/config-v27.json index 137578e0fa..76d25158c4 100644 --- a/test/testdata/configs/config-v27.json +++ b/test/testdata/configs/config-v27.json @@ -28,6 +28,7 @@ "DNSSecurityFlags": 1, "DeadlockDetection": 0, "DeadlockDetectionThreshold": 30, + "DisableLedgerLRUCache": false, "DisableLocalhostConnectionRateLimit": true, "DisableNetworking": false, "DisableOutgoingConnectionThrottling": false,