Merge branch 'increase-online-timestamp-modulus'

This commit is contained in:
CalDescent 2022-07-31 13:00:47 +01:00
commit 06b5d5f1d0
15 changed files with 238 additions and 9 deletions

View File

@ -185,6 +185,10 @@ public class BlockChain {
/** Maximum time to retain online account signatures (ms) for block validity checks, to allow for clock variance. */ /** Maximum time to retain online account signatures (ms) for block validity checks, to allow for clock variance. */
private long onlineAccountSignaturesMaxLifetime; private long onlineAccountSignaturesMaxLifetime;
/** Feature trigger timestamp for ONLINE_ACCOUNTS_MODULUS time interval increase. Can't use
* featureTriggers because unit tests need to set this value via Reflection. */
private long onlineAccountsModulusV2Timestamp;
/** Max reward shares by block height */ /** Max reward shares by block height */
public static class MaxRewardSharesByTimestamp { public static class MaxRewardSharesByTimestamp {
public long timestamp; public long timestamp;
@ -340,6 +344,11 @@ public class BlockChain {
return this.maxBlockSize; return this.maxBlockSize;
} }
// Online accounts
public long getOnlineAccountsModulusV2Timestamp() {
return this.onlineAccountsModulusV2Timestamp;
}
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */ /** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
public boolean getRequireGroupForApproval() { public boolean getRequireGroupForApproval() {
return this.requireGroupForApproval; return this.requireGroupForApproval;

View File

@ -36,7 +36,8 @@ public class OnlineAccountsManager {
/** /**
* How long online accounts signatures last before they expire. * How long online accounts signatures last before they expire.
*/ */
public static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000L; private static final long ONLINE_TIMESTAMP_MODULUS_V1 = 5 * 60 * 1000L;
private static final long ONLINE_TIMESTAMP_MODULUS_V2 = 30 * 60 * 1000L;
/** /**
* How many 'current' timestamp-sets of online accounts we cache. * How many 'current' timestamp-sets of online accounts we cache.
@ -78,12 +79,20 @@ public class OnlineAccountsManager {
private boolean hasOurOnlineAccounts = false; private boolean hasOurOnlineAccounts = false;
public static long getOnlineTimestampModulus() {
Long now = NTP.getTime();
if (now != null && now >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp()) {
return ONLINE_TIMESTAMP_MODULUS_V2;
}
return ONLINE_TIMESTAMP_MODULUS_V1;
}
public static Long getCurrentOnlineAccountTimestamp() { public static Long getCurrentOnlineAccountTimestamp() {
Long now = NTP.getTime(); Long now = NTP.getTime();
if (now == null) if (now == null)
return null; return null;
return (now / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS; long onlineTimestampModulus = getOnlineTimestampModulus();
return (now / onlineTimestampModulus) * onlineTimestampModulus;
} }
private OnlineAccountsManager() { private OnlineAccountsManager() {
@ -203,11 +212,17 @@ public class OnlineAccountsManager {
long onlineAccountTimestamp = onlineAccountData.getTimestamp(); long onlineAccountTimestamp = onlineAccountData.getTimestamp();
// Check timestamp is 'recent' here // Check timestamp is 'recent' here
if (Math.abs(onlineAccountTimestamp - now) > ONLINE_TIMESTAMP_MODULUS * 2) { if (Math.abs(onlineAccountTimestamp - now) > getOnlineTimestampModulus() * 2) {
LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp)); LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp));
return false; return false;
} }
// Check timestamp is a multiple of online timestamp modulus
if (onlineAccountTimestamp % getOnlineTimestampModulus() != 0) {
LOGGER.trace(() -> String.format("Rejecting online account %s with invalid timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp));
return false;
}
// Verify signature // Verify signature
byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp()); byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp());
boolean isSignatureValid = onlineAccountTimestamp >= BlockChain.getInstance().getAggregateSignatureTimestamp() boolean isSignatureValid = onlineAccountTimestamp >= BlockChain.getInstance().getAggregateSignatureTimestamp()
@ -308,7 +323,7 @@ public class OnlineAccountsManager {
if (now == null) if (now == null)
return; return;
final long cutoffThreshold = now - MAX_CACHED_TIMESTAMP_SETS * ONLINE_TIMESTAMP_MODULUS; final long cutoffThreshold = now - MAX_CACHED_TIMESTAMP_SETS * getOnlineTimestampModulus();
this.currentOnlineAccounts.keySet().removeIf(timestamp -> timestamp < cutoffThreshold); this.currentOnlineAccounts.keySet().removeIf(timestamp -> timestamp < cutoffThreshold);
this.currentOnlineAccountsHashes.keySet().removeIf(timestamp -> timestamp < cutoffThreshold); this.currentOnlineAccountsHashes.keySet().removeIf(timestamp -> timestamp < cutoffThreshold);
} }

View File

@ -12,7 +12,6 @@ import java.util.function.Supplier;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.controller.Controller;
import org.qortal.controller.OnlineAccountsManager; import org.qortal.controller.OnlineAccountsManager;
import org.qortal.controller.tradebot.TradeBot; import org.qortal.controller.tradebot.TradeBot;
import org.qortal.crosschain.ACCT; import org.qortal.crosschain.ACCT;
@ -49,7 +48,7 @@ public class PresenceTransaction extends Transaction {
REWARD_SHARE(0) { REWARD_SHARE(0) {
@Override @Override
public long getLifetime() { public long getLifetime() {
return OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS; return OnlineAccountsManager.getOnlineTimestampModulus();
} }
}, },
TRADE_BOT(1) { TRADE_BOT(1) {

View File

@ -23,6 +23,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 43200000, "onlineAccountSignaturesMinLifetime": 43200000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 5.00 }, { "height": 1, "reward": 5.00 },
{ "height": 259201, "reward": 4.75 }, { "height": 259201, "reward": 4.75 },

View File

@ -1,22 +1,36 @@
package org.qortal.test.network; package org.qortal.test.network;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.block.Block;
import org.qortal.block.BlockChain;
import org.qortal.controller.BlockMinter;
import org.qortal.controller.OnlineAccountsManager;
import org.qortal.data.network.OnlineAccountData; import org.qortal.data.network.OnlineAccountData;
import org.qortal.network.message.*; import org.qortal.network.message.*;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings;
import org.qortal.test.common.Common;
import org.qortal.transform.Transformer; import org.qortal.transform.Transformer;
import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.Security; import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;
public class OnlineAccountsTests { public class OnlineAccountsTests extends Common {
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
static { static {
@ -27,6 +41,12 @@ public class OnlineAccountsTests {
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
} }
@Before
public void beforeTest() throws DataException, IOException {
Common.useSettingsAndDb("test-settings-v2-no-sig-agg.json", false);
NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset());
}
@Test @Test
public void testGetOnlineAccountsV2() throws MessageException { public void testGetOnlineAccountsV2() throws MessageException {
@ -111,4 +131,75 @@ public class OnlineAccountsTests {
return onlineAccounts; return onlineAccounts;
} }
@Test
public void testOnlineAccountsModulusV1() throws IllegalAccessException, DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Set feature trigger timestamp to MAX long so that it is inactive
FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsModulusV2Timestamp", Long.MAX_VALUE, true);
List<String> onlineAccountSignatures = new ArrayList<>();
long fakeNTPOffset = 0L;
// Mint a block and store its timestamp
Block block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
long lastBlockTimestamp = block.getBlockData().getTimestamp();
// Mint some blocks and keep track of the different online account signatures
for (int i = 0; i < 30; i++) {
block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
// Increase NTP fixed offset by the block time, to simulate time passing
long blockTimeDelta = block.getBlockData().getTimestamp() - lastBlockTimestamp;
lastBlockTimestamp = block.getBlockData().getTimestamp();
fakeNTPOffset += blockTimeDelta;
NTP.setFixedOffset(fakeNTPOffset);
String lastOnlineAccountSignatures58 = Base58.encode(block.getBlockData().getOnlineAccountsSignatures());
if (!onlineAccountSignatures.contains(lastOnlineAccountSignatures58)) {
onlineAccountSignatures.add(lastOnlineAccountSignatures58);
}
}
// We expect at least 6 unique signatures over 30 blocks (generally 6-8, but could be higher due to block time differences)
System.out.println(String.format("onlineAccountSignatures count: %d", onlineAccountSignatures.size()));
assertTrue(onlineAccountSignatures.size() >= 6);
}
}
@Test
public void testOnlineAccountsModulusV2() throws IllegalAccessException, DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Set feature trigger timestamp to 0 so that it is active
FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsModulusV2Timestamp", 0L, true);
List<String> onlineAccountSignatures = new ArrayList<>();
long fakeNTPOffset = 0L;
// Mint a block and store its timestamp
Block block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
long lastBlockTimestamp = block.getBlockData().getTimestamp();
// Mint some blocks and keep track of the different online account signatures
for (int i = 0; i < 30; i++) {
block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
// Increase NTP fixed offset by the block time, to simulate time passing
long blockTimeDelta = block.getBlockData().getTimestamp() - lastBlockTimestamp;
lastBlockTimestamp = block.getBlockData().getTimestamp();
fakeNTPOffset += blockTimeDelta;
NTP.setFixedOffset(fakeNTPOffset);
String lastOnlineAccountSignatures58 = Base58.encode(block.getBlockData().getOnlineAccountsSignatures());
if (!onlineAccountSignatures.contains(lastOnlineAccountSignatures58)) {
onlineAccountSignatures.add(lastOnlineAccountSignatures58);
}
}
// We expect 1-3 unique signatures over 30 blocks
System.out.println(String.format("onlineAccountSignatures count: %d", onlineAccountSignatures.size()));
assertTrue(onlineAccountSignatures.size() >= 1 && onlineAccountSignatures.size() <= 3);
}
}
} }

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -0,0 +1,87 @@
{
"isTestChain": true,
"blockTimestampMargin": 500,
"transactionExpiryPeriod": 86400000,
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [
{ "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 },
{ "height": 21, "reward": 1 }
],
"sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 }
],
"qoraHoldersShare": 0.20,
"qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
],
"ciyamAtSettings": {
"feePerStep": "0.0001",
"maxStepsPerRound": 500,
"stepsPerFunctionCall": 10,
"minutesPerBlock": 1
},
"featureTriggers": {
"messageHeight": 0,
"atHeight": 0,
"assetsTimestamp": 0,
"votingTimestamp": 0,
"arbitraryTimestamp": 0,
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999,
"rewardShareLimitTimestamp": 9999999999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999,
"aggregateSignatureTimestamp": 9999999999999
},
"genesisInfo": {
"version": 4,
"timestamp": 0,
"transactions": [
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" },
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }
]
}
}

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -18,6 +18,7 @@
"founderEffectiveMintingLevel": 10, "founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -0,0 +1,19 @@
{
"repositoryPath": "testdb",
"bitcoinNet": "TEST3",
"litecoinNet": "TEST3",
"restrictedApi": false,
"blockchainConfig": "src/test/resources/test-chain-v2-no-sig-agg.json",
"exportPath": "qortal-backup-test",
"bootstrap": false,
"wipeUnconfirmedOnStart": false,
"testNtpOffset": 0,
"minPeers": 0,
"pruneBlockLimit": 100,
"bootstrapFilenamePrefix": "test-",
"dataPath": "data-test",
"tempDataPath": "data-test/_temp",
"listsPath": "lists-test",
"storagePolicy": "FOLLOWED_OR_VIEWED",
"maxStorageCapacity": 104857600
}