diff --git a/Lib9c b/Lib9c index 9c378d993..757b1ca80 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 9c378d99369b8c52bc187b563e47a79bdb8cceaa +Subproject commit 757b1ca80eb26ad440fa5f680470e10cdf85cf2c diff --git a/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj b/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj index 4aabfe3ed..07a7df259 100644 --- a/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj +++ b/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/NineChronicles.Headless.Executable/Commands/AccountCommand.cs b/NineChronicles.Headless.Executable/Commands/AccountCommand.cs index 9531c62f4..767b1bea6 100644 --- a/NineChronicles.Headless.Executable/Commands/AccountCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/AccountCommand.cs @@ -75,7 +75,11 @@ public void Balance( _console.Error.WriteLine("Scanning block #{0} {1}...", digest.Index, digest.Hash); _console.Error.Flush(); IEnumerable
addrs = digest.TxIds - .Select(txId => store.GetTransaction(new TxId(txId.ToArray()))) + .Select(txId => + { + return store.GetTransaction(new TxId(txId.ToArray())) ?? + throw new InvalidOperationException($"Transaction #{txId} is not found in the store."); + }) .SelectMany(tx => tx.Actions is { } ca ? ca.Select(a => ToAction(a)) .SelectMany(a => diff --git a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs index 163aacf27..00c25e717 100644 --- a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs @@ -82,7 +82,7 @@ public void Tip( BlockHash tipHash = store.IndexBlockHash(chainId, -1) ?? throw new CommandExitedException("The given chain seems empty.", -1); - Block tip = store.GetBlock(tipHash); + Block tip = GetBlock(store, tipHash); _console.Out.WriteLine(CoconaUtils.SerializeHumanReadable(tip.Header)); store.Dispose(); } @@ -129,7 +129,7 @@ public void Inspect( throw new CommandExitedException($"There is no genesis block: {storePath}", -1); } - Block genesisBlock = store.GetBlock(gHash); + Block genesisBlock = GetBlock(store, gHash); var blockChainStates = new BlockChainStates(store, stateStore); var actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, @@ -163,8 +163,8 @@ public void Inspect( foreach (var item in store.IterateIndexes(chain.Id, offset + 1 ?? 1, limit).Select((value, i) => new { i, value })) { - var block = store.GetBlock(item.value); - var previousBlock = store.GetBlock( + var block = GetBlock(store, item.value); + var previousBlock = GetBlock(store, block.PreviousHash ?? block.Hash ); @@ -270,7 +270,7 @@ public void Truncate( -1); } - var tip = store.GetBlock(tipHash); + var tip = GetBlock(store, tipHash); var snapshotTipIndex = Math.Max(tipIndex - (blocksBefore + 1), 0); BlockHash snapshotTipHash; @@ -286,7 +286,7 @@ public void Truncate( } snapshotTipHash = hash; - } while (!stateStore.GetStateRoot(store.GetBlock(snapshotTipHash).StateRootHash).Recorded); + } while (!stateStore.GetStateRoot(GetBlock(store, snapshotTipHash).StateRootHash).Recorded); var forkedId = Guid.NewGuid(); @@ -484,11 +484,11 @@ public void Snapshot( ); var originalChain = new BlockChain(blockPolicy, stagePolicy, store, stateStore, store.GetBlock(genesisHash), blockChainStates, actionEvaluator); - var tip = store.GetBlock(tipHash); + var tip = GetBlock(store, tipHash); var potentialSnapshotTipIndex = tipIndex - blockBefore; var potentialSnapshotTipHash = (BlockHash)store.IndexBlockHash(chainId, potentialSnapshotTipIndex)!; - var snapshotTip = store.GetBlock(potentialSnapshotTipHash); + var snapshotTip = GetBlock(store, potentialSnapshotTipHash); _console.Out.WriteLine( "Original Store Tip: #{0}\n1. LastCommit: {1}\n2. BlockCommit in Chain: {2}\n3. BlockCommit in Store: {3}", @@ -520,24 +520,38 @@ public void Snapshot( "There is no block commit associated with the potential snapshot tip: #{0}. Snapshot will automatically truncate 1 more block from the original chain tip.", potentialSnapshotTipIndex); blockBefore += 1; - potentialSnapshotTipBlockCommit = store - .GetBlock((BlockHash)store.IndexBlockHash(chainId, tip.Index - blockBefore + 1)!).LastCommit; + potentialSnapshotTipBlockCommit = + GetBlock(store, (BlockHash)store.IndexBlockHash(chainId, tip.Index - blockBefore + 1)!).LastCommit; + if (potentialSnapshotTipBlockCommit == null) + { + throw new CommandExitedException( + $"The block commit of the potential snapshot tip: #{potentialSnapshotTipIndex} doesn't exist.", + -1); + } + store.PutBlockCommit(tipBlockCommit); store.PutChainBlockCommit(chainId, tipBlockCommit); store.PutBlockCommit(potentialSnapshotTipBlockCommit); store.PutChainBlockCommit(chainId, potentialSnapshotTipBlockCommit); } - var blockCommitBlock = store.GetBlock(tipHash); + var blockCommitBlock = GetBlock(store, tipHash); // Add last block commits to store from tip until --block-before + 5 or tip if too short for buffer var blockCommitRange = blockBefore + 5 < tip.Index ? blockBefore + 5 : tip.Index - 1; for (var i = 0; i < blockCommitRange; i++) { _console.Out.WriteLine("Adding block #{0}'s block commit to the store", blockCommitBlock.Index - 1); + if (blockCommitBlock.LastCommit == null) + { + throw new CommandExitedException( + $"The block commit of the block: #{blockCommitBlock.Index} doesn't exist.", + -1); + } + store.PutBlockCommit(blockCommitBlock.LastCommit); store.PutChainBlockCommit(chainId, blockCommitBlock.LastCommit); - blockCommitBlock = store.GetBlock((BlockHash)blockCommitBlock.PreviousHash!); + blockCommitBlock = GetBlock(store, (BlockHash)blockCommitBlock.PreviousHash!); } var snapshotTipIndex = Math.Max(tipIndex - (blockBefore + 1), 0); @@ -555,7 +569,7 @@ public void Snapshot( } snapshotTipHash = hash; - } while (!stateStore.GetStateRoot(store.GetBlock(snapshotTipHash).StateRootHash).Recorded); + } while (!stateStore.GetStateRoot(GetBlock(store, snapshotTipHash).StateRootHash).Recorded); var forkedId = Guid.NewGuid(); Fork(chainId, forkedId, snapshotTipHash, tip, store); @@ -745,6 +759,18 @@ public void Snapshot( } } + private static Block GetBlock(IStore store, BlockHash blockHash) + { + return store.GetBlock(blockHash) ?? + throw new CommandExitedException($"The block of {blockHash} doesn't exist.", -1); + } + + private static BlockCommit GetBlockCommit(IStore store, BlockHash blockHash) + { + return store.GetBlockCommit(blockHash) ?? + throw new CommandExitedException($"The block commit of {blockHash} doesn't exist.", -1); + } + private string GetPartitionBaseFileName( int currentMetadataBlockEpoch, int currentMetadataTxEpoch, @@ -1025,7 +1051,7 @@ private void Fork( store.ForkBlockIndexes(src, dest, branchPointHash); if (store.GetBlockCommit(branchPointHash) is { }) { - store.PutChainBlockCommit(dest, store.GetBlockCommit(branchPointHash)); + store.PutChainBlockCommit(dest, GetBlockCommit(store, branchPointHash)); } store.ForkTxNonces(src, dest); @@ -1033,7 +1059,7 @@ private void Fork( Block block = tip; block.PreviousHash is { } hash && !block.Hash.Equals(branchPointHash); - block = store.GetBlock(hash)) + block = GetBlock(store, hash)) { IEnumerable<(Address, int)> signers = block .Transactions diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 28a930868..9c7eba6dd 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -121,7 +121,8 @@ IStateStore stateStore } Block block = - store.GetBlock(blockHash); + store.GetBlock(blockHash) ?? + throw new CommandExitedException($"The block of {blockHash} doesn't exist.", -1); var preEvalBlock = new PreEvaluationBlock( block, block.Transactions diff --git a/NineChronicles.Headless.Executable/Store/StoreExtensions.cs b/NineChronicles.Headless.Executable/Store/StoreExtensions.cs index e2d56f7ee..8a4186adf 100644 --- a/NineChronicles.Headless.Executable/Store/StoreExtensions.cs +++ b/NineChronicles.Headless.Executable/Store/StoreExtensions.cs @@ -18,7 +18,12 @@ public static Block GetGenesisBlock(this IStore store) } BlockHash genesisBlockHash = store.IterateIndexes(chainId.Value).First(); - Block genesisBlock = store.GetBlock(genesisBlockHash); + Block? genesisBlock = store.GetBlock(genesisBlockHash); + if (genesisBlock == null) + { + throw new InvalidOperationException("The store doesn't have genesis block."); + } + return genesisBlock; } } diff --git a/NineChronicles.Headless.Tests/ArenaParticipantsWorkerTest.cs b/NineChronicles.Headless.Tests/ArenaParticipantsWorkerTest.cs index 35c76bed0..9e7d62b87 100644 --- a/NineChronicles.Headless.Tests/ArenaParticipantsWorkerTest.cs +++ b/NineChronicles.Headless.Tests/ArenaParticipantsWorkerTest.cs @@ -4,6 +4,7 @@ using Lib9c.Tests; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.Mocks; using Nekoyume; using Nekoyume.Action; using Nekoyume.Model.Arena; @@ -25,7 +26,7 @@ public class ArenaParticipantsWorkerTest public ArenaParticipantsWorkerTest() { - _world = new MockWorld(new MockWorldState()); + _world = new World(MockWorldState.CreateModern()); _sheets = TableSheetsImporter.ImportSheets(); } diff --git a/NineChronicles.Headless.Tests/Common/MockAccount.cs b/NineChronicles.Headless.Tests/Common/MockAccount.cs deleted file mode 100644 index ac2d3acdd..000000000 --- a/NineChronicles.Headless.Tests/Common/MockAccount.cs +++ /dev/null @@ -1,295 +0,0 @@ -namespace NineChronicles.Headless.Tests.Common -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Diagnostics.Contracts; - using System.Numerics; - using Bencodex.Types; - using Libplanet.Action; - using Libplanet.Action.State; - using Libplanet.Crypto; - using Libplanet.Store.Trie; - using Libplanet.Types.Assets; - using Libplanet.Types.Consensus; - using static KeyConverters; - - /// - /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/Account.cs - /// except this has its constructors exposed as public for testing. - /// - [Pure] - public class MockAccount : IAccount - { - private readonly MockAccountState _state; - - public MockAccount(MockAccountState state) - : this(state, ImmutableHashSet<(Address, Currency)>.Empty) - { - } - - public MockAccount( - MockAccountState state, - IImmutableSet<(Address, Currency)> totalUpdatedFungibleAssets) - { - _state = state; - TotalUpdatedFungibleAssets = totalUpdatedFungibleAssets; - } - - /// - public ITrie Trie => throw new NotSupportedException(); - - public IImmutableDictionary MockTrie => _state.MockTrie; - - /// - public IImmutableSet<(Address, Currency)> TotalUpdatedFungibleAssets { get; } - - /// - [Pure] - public IValue? GetState(Address address) => _state.GetState(address); - - /// - [Pure] - public IReadOnlyList GetStates(IReadOnlyList
addresses) => - _state.GetStates(addresses); - - /// - [Pure] - public IAccount SetState(Address address, IValue state) => UpdateState(address, state); - - /// - [Pure] - public IAccount RemoveState(Address address) => UpdateState(address); - - /// - [Pure] - public FungibleAssetValue GetBalance(Address address, Currency currency) => - _state.GetBalance(address, currency); - - /// - [Pure] - public FungibleAssetValue GetTotalSupply(Currency currency) => - _state.GetTotalSupply(currency); - - /// - [Pure] - public ValidatorSet GetValidatorSet() => _state.GetValidatorSet(); - - /// - [Pure] - public IAccount MintAsset( - IActionContext context, Address recipient, FungibleAssetValue value) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to mint has to be greater than zero." - ); - } - - Currency currency = value.Currency; - if (!currency.AllowsToMint(context.Signer)) - { - throw new CurrencyPermissionException( - $"The account {context.Signer} has no permission to mint currency {currency}.", - context.Signer, - currency - ); - } - - FungibleAssetValue balance = GetBalance(recipient, currency); - BigInteger rawBalance = (balance + value).RawValue; - - if (currency.TotalSupplyTrackable) - { - var currentTotalSupply = GetTotalSupply(currency); - if (currency.MaximumSupply < currentTotalSupply + value) - { - var msg = $"The amount {value} attempted to be minted added to the current" - + $" total supply of {currentTotalSupply} exceeds the" - + $" maximum allowed supply of {currency.MaximumSupply}."; - throw new SupplyOverflowException(msg, value); - } - - return UpdateFungibleAssets( - recipient, - currency, - rawBalance, - (currentTotalSupply + value).RawValue); - } - else - { - return UpdateFungibleAssets(recipient, currency, rawBalance); - } - } - - /// - [Pure] - public IAccount TransferAsset( - IActionContext context, - Address sender, - Address recipient, - FungibleAssetValue value, - bool allowNegativeBalance = false) => context.BlockProtocolVersion > 0 - ? TransferAssetV1(sender, recipient, value, allowNegativeBalance) - : TransferAssetV0(sender, recipient, value, allowNegativeBalance); - - /// - [Pure] - public IAccount BurnAsset( - IActionContext context, Address owner, FungibleAssetValue value) - { - string msg; - - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to burn has to be greater than zero." - ); - } - - Currency currency = value.Currency; - if (!currency.AllowsToMint(context.Signer)) - { - msg = $"The account {context.Signer} has no permission to burn assets of " + - $"the currency {currency}."; - throw new CurrencyPermissionException(msg, context.Signer, currency); - } - - FungibleAssetValue balance = GetBalance(owner, currency); - - if (balance < value) - { - msg = $"The account {owner}'s balance of {currency} is insufficient to burn: " + - $"{balance} < {value}."; - throw new InsufficientBalanceException(msg, owner, balance); - } - - BigInteger rawBalance = (balance - value).RawValue; - if (currency.TotalSupplyTrackable) - { - var currentTotalSupply = GetTotalSupply(currency); - return UpdateFungibleAssets( - owner, - currency, - rawBalance, - (currentTotalSupply - value).RawValue); - } - else - { - return UpdateFungibleAssets(owner, currency, rawBalance); - } - } - - /// - [Pure] - public IAccount SetValidator(Validator validator) => - UpdateValidatorSet(GetValidatorSet().Update(validator)); - - [Pure] - private MockAccount UpdateState( - Address address, - IValue value) => - new MockAccount( - new MockAccountState( - MockTrie.Add(ToStateKey(address), value)), - TotalUpdatedFungibleAssets); - - [Pure] - private MockAccount UpdateState( - Address address) => - new MockAccount( - new MockAccountState( - MockTrie.Remove(ToStateKey(address))), - TotalUpdatedFungibleAssets); - - [Pure] - private MockAccount UpdateFungibleAssets( - Address address, - Currency currency, - BigInteger amount, - BigInteger? supplyAmount = null) => supplyAmount is { } sa - ? new MockAccount( - new MockAccountState( - MockTrie - .Add(ToFungibleAssetKey(address, currency), new Integer(amount)) - .Add(ToTotalSupplyKey(currency), new Integer(sa))), - TotalUpdatedFungibleAssets.Add((address, currency))) - : new MockAccount( - new MockAccountState( - MockTrie.Add(ToFungibleAssetKey(address, currency), new Integer(amount))), - TotalUpdatedFungibleAssets.Add((address, currency))); - - [Pure] - private MockAccount UpdateValidatorSet(ValidatorSet validatorSet) => - new MockAccount( - new MockAccountState( - MockTrie.Add(ValidatorSetKey, validatorSet.Bencoded)), - TotalUpdatedFungibleAssets); - - [Pure] - private IAccount TransferAssetV0( - Address sender, - Address recipient, - FungibleAssetValue value, - bool allowNegativeBalance = false) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to transfer has to be greater than zero." - ); - } - - Currency currency = value.Currency; - FungibleAssetValue senderBalance = GetBalance(sender, currency); - FungibleAssetValue recipientBalance = GetBalance(recipient, currency); - - if (!allowNegativeBalance && senderBalance < value) - { - var msg = $"The account {sender}'s balance of {currency} is insufficient to " + - $"transfer: {senderBalance} < {value}."; - throw new InsufficientBalanceException(msg, sender, senderBalance); - } - - return UpdateFungibleAssets(sender, currency, (senderBalance - value).RawValue) - .UpdateFungibleAssets(recipient, currency, (recipientBalance + value).RawValue); - } - - [Pure] - private IAccount TransferAssetV1( - Address sender, - Address recipient, - FungibleAssetValue value, - bool allowNegativeBalance = false) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to transfer has to be greater than zero." - ); - } - - Currency currency = value.Currency; - FungibleAssetValue senderBalance = GetBalance(sender, currency); - - if (!allowNegativeBalance && senderBalance < value) - { - var msg = $"The account {sender}'s balance of {currency} is insufficient to " + - $"transfer: {senderBalance} < {value}."; - throw new InsufficientBalanceException(msg, sender, senderBalance); - } - - BigInteger senderRawBalance = (senderBalance - value).RawValue; - MockAccount intermediate = UpdateFungibleAssets(sender, currency, senderRawBalance); - FungibleAssetValue recipientBalance = intermediate.GetBalance(recipient, currency); - BigInteger recipientRawBalance = (recipientBalance + value).RawValue; - - return intermediate.UpdateFungibleAssets(recipient, currency, recipientRawBalance); - } - } -} diff --git a/NineChronicles.Headless.Tests/Common/MockAccountState.cs b/NineChronicles.Headless.Tests/Common/MockAccountState.cs deleted file mode 100644 index 0523cbc94..000000000 --- a/NineChronicles.Headless.Tests/Common/MockAccountState.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Numerics; -using Bencodex.Types; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Libplanet.Store.Trie; -using Libplanet.Types.Assets; -using Libplanet.Types.Consensus; -using static NineChronicles.Headless.Tests.Common.KeyConverters; - -namespace NineChronicles.Headless.Tests.Common -{ - /// - /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/AccountState.cs - /// except this has its constructors exposed as public for testing. - /// - public class MockAccountState : IAccountState - { - public MockAccountState() : this(ImmutableDictionary.Empty) - { - } - - public MockAccountState(IImmutableDictionary mockTrie) - { - MockTrie = mockTrie; - } - - /// - public ITrie Trie => throw new NotSupportedException(); - - /// - public IImmutableDictionary MockTrie { get; } - - /// - public IValue? GetState(Address address) => MockTrie.TryGetValue(ToStateKey(address), out var v) - ? v - : null; - - /// - public IReadOnlyList GetStates(IReadOnlyList
addresses) => - addresses.Select(address => GetState(address)).ToList(); - - /// - public FungibleAssetValue GetBalance(Address address, Currency currency) - { - IValue? value = MockTrie.TryGetValue(ToFungibleAssetKey(address, currency), out var v) - ? v - : null; - return value is Integer i - ? FungibleAssetValue.FromRawValue(currency, i) - : currency * 0; - } - - /// - public FungibleAssetValue GetTotalSupply(Currency currency) - { - if (!currency.TotalSupplyTrackable) - { - throw TotalSupplyNotTrackableException.WithDefaultMessage(currency); - } - - IValue? value = MockTrie.TryGetValue(ToTotalSupplyKey(currency), out var v) - ? v - : null; - return value is Integer i - ? FungibleAssetValue.FromRawValue(currency, i) - : currency * 0; - } - - /// - public ValidatorSet GetValidatorSet() - { - IValue? value = MockTrie.TryGetValue(ValidatorSetKey, out var v) - ? v - : null; - return value is List list - ? new ValidatorSet(list) - : new ValidatorSet(); - } - - // Methods used in unit tests - public MockAccountState SetState(Address address, IValue state) => - new MockAccountState(MockTrie.SetItem(ToStateKey(address), state)); - - public MockAccountState SetBalance(Address address, FungibleAssetValue amount) => - SetBalance((address, amount.Currency), amount.RawValue); - - public MockAccountState SetBalance(Address address, Currency currency, BigInteger rawAmount) => - SetBalance((address, currency), rawAmount); - - public MockAccountState SetBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) => - new MockAccountState(MockTrie.SetItem(ToFungibleAssetKey(pair), (Integer)rawAmount)); - - public MockAccountState AddBalance(Address address, FungibleAssetValue amount) => - AddBalance((address, amount.Currency), amount.RawValue); - - public MockAccountState AddBalance(Address address, Currency currency, BigInteger rawAmount) => - AddBalance((address, currency), rawAmount); - - public MockAccountState AddBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) - { - var amount = GetBalance(pair.Address, pair.Currency).RawValue + rawAmount; - return SetBalance(pair, amount); - } - - public MockAccountState SubtractBalance(Address address, FungibleAssetValue amount) => - SubtractBalance((address, amount.Currency), amount.RawValue); - - public MockAccountState SubtractBalance(Address address, Currency currency, BigInteger rawAmount) => - SubtractBalance((address, currency), rawAmount); - - public MockAccountState SubtractBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) - { - var amount = GetBalance(pair.Address, pair.Currency).RawValue - rawAmount; - return SetBalance(pair, amount); - } - - public MockAccountState TransferBalance(Address sender, Address recipient, FungibleAssetValue amount) => - TransferBalance(sender, recipient, amount.Currency, amount.RawValue); - - public MockAccountState TransferBalance(Address sender, Address recipient, Currency currency, BigInteger rawAmount) => - SubtractBalance(sender, currency, rawAmount).AddBalance(recipient, currency, rawAmount); - - public MockAccountState SetTotalSupply(FungibleAssetValue amount) => - SetTotalSupply(amount.Currency, amount.RawValue); - - public MockAccountState SetTotalSupply(Currency currency, BigInteger rawAmount) => - currency.TotalSupplyTrackable - ? !(currency.MaximumSupply is { } maximumSupply) || rawAmount <= maximumSupply.RawValue - ? new MockAccountState(MockTrie.SetItem(ToTotalSupplyKey(currency), (Integer)rawAmount)) - : throw new ArgumentException( - $"Given {currency}'s total supply is capped at {maximumSupply.RawValue} and " + - $"cannot be set to {rawAmount}.") - : throw new ArgumentException( - $"Given {currency} is not trackable."); - - public MockAccountState AddTotalSupply(FungibleAssetValue amount) => - AddTotalSupply(amount.Currency, amount.RawValue); - - public MockAccountState AddTotalSupply(Currency currency, BigInteger rawAmount) - { - var amount = GetTotalSupply(currency).RawValue + rawAmount; - return SetTotalSupply(currency, amount); - } - - public MockAccountState SubtractTotalSupply(FungibleAssetValue amount) => - SubtractTotalSupply(amount.Currency, amount.RawValue); - - public MockAccountState SubtractTotalSupply(Currency currency, BigInteger rawAmount) - { - var amount = GetTotalSupply(currency).RawValue - rawAmount; - return SetTotalSupply(currency, amount); - } - - public MockAccountState SetValidator(Validator validator) => - new MockAccountState(MockTrie.SetItem(ValidatorSetKey, GetValidatorSet().Update(validator).Bencoded)); - } -} diff --git a/NineChronicles.Headless.Tests/Common/MockWorld.cs b/NineChronicles.Headless.Tests/Common/MockWorld.cs deleted file mode 100644 index c31e6f723..000000000 --- a/NineChronicles.Headless.Tests/Common/MockWorld.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Libplanet.Store.Trie; - -namespace NineChronicles.Headless.Tests.Common -{ - using System; - using System.Diagnostics.Contracts; - using Libplanet.Action.State; - using Libplanet.Crypto; - - /// - /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/World.cs - /// except this has its constructors exposed as public for testing. - /// - [Pure] - public class MockWorld : IWorld - { - private readonly IWorldState _baseState; - - public MockWorld(IWorldState baseState) - : this(baseState, new MockWorldDelta()) - { - } - - public MockWorld( - IWorldState baseState, - IWorldDelta delta) - { - _baseState = baseState; - Delta = delta; - Legacy = baseState.Legacy; - } - - /// - public IWorldDelta Delta { get; } - - /// - [Pure] - public ITrie Trie => _baseState.Trie; - - /// - [Pure] - public bool Legacy { get; private set; } - - /// - [Pure] - public IAccount GetAccount(Address address) - { - if (Delta.Accounts.TryGetValue(address, out IAccount? account)) - { - return account; - } - else - { - switch (_baseState.GetAccountState(address)) - { - case MockAccount mockAccount: - return mockAccount; - case MockAccountState mockAccountState: - return new MockAccount(mockAccountState); - default: - throw new InvalidOperationException(); - } - } - } - - public IAccountState GetAccountState(Address address) => GetAccount(address); - - /// - [Pure] - public IWorld SetAccount(Address address, IAccount account) - { - if (!address.Equals(ReservedAddresses.LegacyAccount) - && account.TotalUpdatedFungibleAssets.Count > 0) - { - return this; - } - - return new MockWorld( - this, - Delta.SetAccount(address, account)); - } - } -} diff --git a/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs b/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs deleted file mode 100644 index e354b0acd..000000000 --- a/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; - -namespace NineChronicles.Headless.Tests.Common -{ - using System.Collections.Immutable; - using Libplanet.Action.State; - using Libplanet.Crypto; - - /// - /// Almost a replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/WorldDelta.cs - /// except this has its constructors exposed as public for testing. - /// - public class MockWorldDelta : IWorldDelta - { - private IImmutableDictionary _accounts; - - public MockWorldDelta() - { - _accounts = ImmutableDictionary.Empty; - } - - private MockWorldDelta(IImmutableDictionary accounts) - { - _accounts = accounts; - } - - /// - public IImmutableDictionary Accounts - => _accounts - .ToImmutableDictionary(item => item.Key, item => item.Value.Account); - - /// - public IImmutableDictionary Uncommitted - => _accounts - .Where(item => !item.Value.Committed) - .ToImmutableDictionary(item => item.Key, item => item.Value.Account); - - /// - public IWorldDelta SetAccount(Address address, IAccount account) - => new MockWorldDelta(_accounts.SetItem(address, new AccountItem(account, false))); - - /// - public IWorldDelta CommitAccount(Address address) - => _accounts.TryGetValue(address, out AccountItem accountItem) - ? new MockWorldDelta( - _accounts.SetItem(address, new AccountItem(accountItem.Account, true))) - : this; - - internal struct AccountItem - { - public AccountItem(IAccount account, bool committed) - { - Account = account; - Committed = committed; - } - - public IAccount Account { get; } - - public bool Committed { get; set; } - } - } -} diff --git a/NineChronicles.Headless.Tests/Common/MockWorldState.cs b/NineChronicles.Headless.Tests/Common/MockWorldState.cs deleted file mode 100644 index 75713585c..000000000 --- a/NineChronicles.Headless.Tests/Common/MockWorldState.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Libplanet.Store.Trie; - -namespace NineChronicles.Headless.Tests.Common -{ -#nullable enable - - using System.Collections.Immutable; - using Libplanet.Action.State; - using Libplanet.Crypto; - - public class MockWorldState : IWorldState - { - private readonly IImmutableDictionary _accounts; - - public MockWorldState() - : this(ImmutableDictionary.Empty) - { - } - - public MockWorldState(IImmutableDictionary accounts) - { - _accounts = accounts; - } - - public ITrie Trie { get; } - public bool Legacy => true; - - public IImmutableDictionary Accounts => _accounts; - - public IAccountState GetAccountState(Address address) => _accounts.TryGetValue(address, out IAccount? account) - ? account - : new MockAccount(new MockAccountState()); - } -} diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index 7a96f1766..a728e2d1a 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -12,6 +12,7 @@ using Libplanet.Action.State; using Libplanet.Common; using Libplanet.Crypto; +using Libplanet.Mocks; using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Model.Elemental; @@ -92,10 +93,11 @@ public async Task Garage( 2, new Address("0x47D082a115c63E7b58B1532d20E631538eaFADde")); #pragma warning restore CS0618 - MockAccountState mockAccountState = new MockAccountState(); + MockWorldState mockWorldState = MockWorldState.CreateModern(); + IAccount account = new Account(mockWorldState.GetAccountState(ReservedAddresses.LegacyAccount)); // NCG - mockAccountState = mockAccountState + account = account .SetState( Addresses.GoldCurrency, new GoldCurrencyState(goldCurrency).Serialize()); @@ -122,7 +124,7 @@ public async Task Garage( .SetItem("elemental_type", ElementalType.Normal.Serialize()) .SetItem("item_id", HashDigest.FromString(fid).Serialize())); - mockAccountState = mockAccountState + account = account .SetState( Addresses.GetGarageAddress( agentAddr, @@ -137,7 +139,8 @@ public async Task Garage( // testing without setting up any balance passes the tests; // also this is different from the original test setup as there is no way // to allow state to have "infinite" FAVs with all possible addresses having FAVs - mockAccountState = mockAccountState + mockWorldState = mockWorldState + .SetAccount(ReservedAddresses.LegacyAccount, account) .SetBalance(agentAddr, new FungibleAssetValue(goldCurrency, 99, 99)) .SetBalance(agentAddr, new FungibleAssetValue(Currencies.Crystal, 99, 123456789012345678)) .SetBalance(agentAddr, new FungibleAssetValue(Currencies.Garage, 99, 123456789012345678)) @@ -146,10 +149,7 @@ public async Task Garage( var queryResult = await ExecuteQueryAsync( sb.ToString(), source: new StateContext( - new MockWorld(new MockWorldState( - ImmutableDictionary.Empty.Add( - ReservedAddresses.LegacyAccount, - new MockAccount(mockAccountState)))), + new World(mockWorldState), 0L, new StateMemoryCache())); Assert.Null(queryResult.Errors); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; @@ -245,7 +245,7 @@ public async Task CachedSheet(bool cached, string? expected) var queryResult = await ExecuteQueryAsync( query, source: new StateContext( - new MockWorld(new MockWorldState()), + new World(MockWorldState.CreateModern()), 0L, cache)); Assert.Null(queryResult.Errors); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs index ec601bf20..be3a97025 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs @@ -5,6 +5,7 @@ using GraphQL.Execution; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.Mocks; using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Action; @@ -60,21 +61,23 @@ public async Task Query(int goldBalance, string goldDecimalString, int crystalBa MonsterCollectionState monsterCollectionState = new MonsterCollectionState(monsterCollectionAddress, 7, 0, Fixtures.TableSheetsFX.MonsterCollectionRewardSheet); Address pledgeAddress = agentState.address.GetPledgeAddress(); - MockAccountState mockAccountState = new MockAccountState() - .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize()) - .SetState(monsterCollectionAddress, monsterCollectionState.Serialize()) - .SetState( - pledgeAddress, - List.Empty - .Add(MeadConfig.PatronAddress.Serialize()) - .Add(true.Serialize()) - .Add(4.Serialize())) + MockWorldState mockWorldState = MockWorldState.CreateModern(); + mockWorldState = mockWorldState + .SetAccount( + ReservedAddresses.LegacyAccount, + new Account(mockWorldState.GetAccountState(ReservedAddresses.LegacyAccount)) + .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize()) + .SetState(monsterCollectionAddress, monsterCollectionState.Serialize()) + .SetState( + pledgeAddress, + List.Empty + .Add(MeadConfig.PatronAddress.Serialize()) + .Add(true.Serialize()) + .Add(4.Serialize()))) .SetBalance(agentState.address, CrystalCalculator.CRYSTAL * crystalBalance) .SetBalance(agentState.address, goldCurrency * goldBalance); - IWorld mockWorld = new MockWorld(new MockWorldState(ImmutableDictionary.Empty.Add( - ReservedAddresses.LegacyAccount, - new MockAccount(mockAccountState)))); - mockWorld = mockWorld.SetAvatarState( + IWorld world = new World(mockWorldState); + world = world.SetAvatarState( Fixtures.AvatarAddress, Fixtures.AvatarStateFX, true, @@ -84,7 +87,7 @@ public async Task Query(int goldBalance, string goldDecimalString, int crystalBa var queryResult = await ExecuteQueryAsync( query, - source: new AgentStateType.AgentStateContext(agentState, mockWorld, 0, new StateMemoryCache()) + source: new AgentStateType.AgentStateContext(agentState, world, 0, new StateMemoryCache()) ); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; var expected = new Dictionary() diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index 0ebc50e03..ddbd69d8d 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -6,6 +6,7 @@ using Lib9c; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.Mocks; using Nekoyume; using Nekoyume.Action; using Nekoyume.Model.State; @@ -30,7 +31,7 @@ public async Task Query(AvatarState avatarState, Dictionary expe index inventoryAddress }"; - IWorld mockWorld = new MockWorld(new MockWorldState()); + IWorld mockWorld = new World(MockWorldState.CreateModern()); mockWorld = mockWorld.SetAvatarState( Fixtures.AvatarAddress, Fixtures.AvatarStateFX, @@ -65,19 +66,19 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction } } "; - IWorld mockWorld = new MockWorld(new MockWorldState()); - mockWorld = mockWorld.SetAvatarState( + IWorld world = new World(MockWorldState.CreateModern()); + world = world.SetAvatarState( Fixtures.AvatarAddress, Fixtures.AvatarStateFX, true, true, true, true); - mockWorld = mockWorld.SetAgentState(Fixtures.UserAddress, Fixtures.AgentStateFx); + world = world.SetAgentState(Fixtures.UserAddress, Fixtures.AgentStateFx); for (int i = 0; i < Fixtures.AvatarStateFX.combinationSlotAddresses.Count; i++) { - mockWorld = mockWorld + world = world .SetLegacyState( Fixtures.AvatarStateFX.combinationSlotAddresses[i], Fixtures.CombinationSlotStatesFx[i].Serialize()); @@ -87,7 +88,7 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction query, source: new AvatarStateType.AvatarStateContext( avatarState, - mockWorld, + world, 0, new StateMemoryCache())); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs index 9619249e9..59ebf1cee 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs @@ -4,6 +4,7 @@ using GraphQL.Execution; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.Mocks; using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Model.Stake; @@ -27,8 +28,12 @@ public async Task Query(StakeStateV2 stakeState, Address stakeStateAddress, long var goldCurrency = Currency.Legacy("NCG", 2, null); #pragma warning restore CS0618 - MockAccountState mockAccountState = new MockAccountState() - .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize()) + MockWorldState mockWorldState = MockWorldState.CreateModern(); + mockWorldState = mockWorldState + .SetAccount( + ReservedAddresses.LegacyAccount, + new Account(mockWorldState.GetAccountState(ReservedAddresses.LegacyAccount)) + .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize())) .SetBalance(Fixtures.StakeStateAddress, goldCurrency, (goldCurrency * deposit).RawValue); const string query = @" @@ -45,10 +50,7 @@ public async Task Query(StakeStateV2 stakeState, Address stakeStateAddress, long source: new StakeStateType.StakeStateContext( stakeState, stakeStateAddress, - new MockWorld(new MockWorldState( - ImmutableDictionary.Empty.Add( - ReservedAddresses.LegacyAccount, - new MockAccount(mockAccountState)))), + new World(mockWorldState), blockIndex, new StateMemoryCache())); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); diff --git a/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs b/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs index 8078cda5f..f1ad705bb 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs @@ -7,6 +7,7 @@ using GraphQL.Execution; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.Mocks; using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Action; @@ -301,9 +302,10 @@ public async Task RaiderList(bool stateExist, long blockIndex, bool prev) private IWorldState GetMockState() { - return new MockWorld(new MockWorldState(ImmutableDictionary.Empty.Add( + MockWorldState mockWorldState = MockWorldState.CreateModern(); + return new World(mockWorldState.SetAccount( ReservedAddresses.LegacyAccount, - new MockAccount(new MockAccountState() + new Account(mockWorldState.GetAccountState(ReservedAddresses.LegacyAccount)) .SetState(_raiderStateAddress, _raiderState.Serialize()) .SetState(Addresses.GetSheetAddress(), @"id,boss_id,started_block_index,ended_block_index,fee,ticket_price,additional_ticket_price,max_purchase_count @@ -312,7 +314,7 @@ private IWorldState GetMockState() ".Serialize()) .SetState(_worldBossAddress, _worldBossState.Serialize()) .SetState(_worldBossKillRewardRecordAddress, _worldBossKillRewardRecord.Serialize()) - .SetState(_raiderListAddress, List.Empty.Add(_raiderStateAddress.Serialize())))))); + .SetState(_raiderListAddress, List.Empty.Add(_raiderStateAddress.Serialize())))); } private async Task GetRaidId(long blockIndex, bool prev) diff --git a/NineChronicles.Headless/BlockChainContext.cs b/NineChronicles.Headless/BlockChainContext.cs index 0fc1eb096..17a53f10a 100644 --- a/NineChronicles.Headless/BlockChainContext.cs +++ b/NineChronicles.Headless/BlockChainContext.cs @@ -16,9 +16,9 @@ public BlockChainContext(StandaloneContext standaloneContext) } public bool Preloaded => _standaloneContext.NodeStatus.PreloadEnded; - public BlockChain? BlockChain => _standaloneContext.BlockChain; - public IStore? Store => _standaloneContext.Store; - public Swarm? Swarm => _standaloneContext.Swarm; + public BlockChain BlockChain => _standaloneContext.BlockChain; + public IStore Store => _standaloneContext.Store; + public Swarm Swarm => _standaloneContext.Swarm; public IBlockChainIndex Index => new RocksDbBlockChainIndex("/tmp/no/no/no/store"); } } diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 237c3e9f0..18989f137 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -323,7 +323,6 @@ public UnaryResult GetBalanceByBlockHash( Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain .GetWorldState(blockHash) - .GetAccountState(ReservedAddresses.LegacyAccount) .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( @@ -348,7 +347,6 @@ public UnaryResult GetBalanceByStateRootHash( Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain .GetWorldState(stateRootHash) - .GetAccountState(ReservedAddresses.LegacyAccount) .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( diff --git a/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs b/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs index 87cb9b00a..8e42ea062 100644 --- a/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs +++ b/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs @@ -22,7 +22,10 @@ public AppProtocolVersionType() resolve: context => context.Source.Signature.ToBuilder().ToArray()); Field( name: "extra", - resolve: context => _codec.Encode(context.Source.Extra)); + resolve: context + => context.Source.Extra != null ? + _codec.Encode(context.Source.Extra) : + null); } } } diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index afd2f7471..463441863 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -134,13 +134,22 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi IEnumerable blockTxs = digest.TxIds .Select(bytes => new TxId(bytes)) - .Select(store.GetTransaction); + .Select(txid => + { + return store.GetTransaction(txid) ?? + throw new InvalidOperationException($"Transaction {txid} not found."); + }); var filtered = blockTxs .Where(tx => tx.Actions.Count == 1) - .Select(tx => (store.GetTxExecution(blockHash, tx.Id), ToAction(tx.Actions[0]))) - .Where(pair => pair.Item1 is { } && pair.Item2 is ITransferAsset) - .Select(pair => (pair.Item1, (ITransferAsset)pair.Item2)) + .Select(tx => + ( + store.GetTxExecution(blockHash, tx.Id) ?? + throw new InvalidOperationException($"TxExecution {tx.Id} not found."), + ToAction(tx.Actions[0]) + )) + .Where(pair => pair.Item2 is ITransferAsset) + .Select(pair => (pair.Item1!, (ITransferAsset)pair.Item2)) .Where(pair => !pair.Item1.Fail && (!recipient.HasValue || pair.Item2.Recipient == recipient) && pair.Item2.Amount.Currency.Ticker == "NCG"); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 0b5cb3694..f0d278ecc 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -295,7 +295,8 @@ private IObservable SubscribeTx(IResolveFieldContext context) { return null; } - var txExecution = store.GetTxExecution(blockHash, transaction.Id); + var txExecution = store.GetTxExecution(blockHash, transaction.Id) ?? + throw new InvalidOperationException("Not found tx execution."); var txExecutedBlock = chain[blockHash]; return new TxResult( txExecution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, diff --git a/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs b/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs index 5121e961b..7d011ae97 100644 --- a/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs +++ b/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs @@ -112,7 +112,9 @@ public static LibplanetNodeServiceProperties Host = swarmHost, Port = swarmPort, SwarmPrivateKey = swarmPrivateKey, - AppProtocolVersion = AppProtocolVersion.FromToken(appProtocolVersionToken), + AppProtocolVersion = AppProtocolVersion.FromToken( + appProtocolVersionToken ?? + throw new InvalidOperationException("appProtocolVersionToken cannot be null.")), TrustedAppProtocolVersionSigners = trustedAppProtocolVersionSigners ?.Select(s => new PublicKey(ByteUtil.ParseHex(s))) ?.ToHashSet(), diff --git a/NineChronicles.Headless/StandaloneContext.cs b/NineChronicles.Headless/StandaloneContext.cs index 31e46dc84..8faad9946 100644 --- a/NineChronicles.Headless/StandaloneContext.cs +++ b/NineChronicles.Headless/StandaloneContext.cs @@ -15,8 +15,23 @@ namespace NineChronicles.Headless { public class StandaloneContext { - public BlockChain? BlockChain { get; set; } - public IKeyStore? KeyStore { get; set; } + private BlockChain? _blockChain; + private IKeyStore? _keyStore; + private IStore? _store; + private Swarm? _swarm; + + public BlockChain BlockChain + { + get => _blockChain ?? + throw new InvalidOperationException($"{nameof(BlockChain)} property is not set yet."); + set => _blockChain = value; + } + public IKeyStore KeyStore + { + get => _keyStore ?? + throw new InvalidOperationException($"{nameof(KeyStore)} property is not set yet."); + set => _keyStore = value; + } public bool BootstrapEnded { get; set; } public bool PreloadEnded { get; set; } public bool IsMining { get; set; } @@ -42,9 +57,19 @@ public class StandaloneContext IsMining = IsMining, }; - public IStore? Store { get; internal set; } + public IStore Store + { + get => _store ?? + throw new InvalidOperationException($"{nameof(Store)} property is not set yet."); + internal set => _store = value; + } - public Swarm? Swarm { get; internal set; } + public Swarm Swarm + { + get => _swarm ?? + throw new InvalidOperationException($"{nameof(Swarm)} property is not set yet."); + internal set => _swarm = value; + } public CurrencyFactory? CurrencyFactory { get; set; }