Skip to content

Commit

Permalink
Merge pull request planetarium#82 from earlbread/fix-block-policy
Browse files Browse the repository at this point in the history
Use the minimum difficulty in blocks for authorized miners
  • Loading branch information
earlbread committed Sep 21, 2020
2 parents a66f5be + 286151d commit b11ab6b
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 71 deletions.
119 changes: 114 additions & 5 deletions .Lib9c.Tests/BlockPolicyTest.cs
@@ -1,5 +1,6 @@
namespace Lib9c.Tests
{
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Bencodex.Types;
Expand Down Expand Up @@ -155,9 +156,12 @@ public async Task ValidateNextBlockWithAuthorizedMinersState()
renderers: new[] { blockPolicySource.BlockRenderer }
);

blockPolicySource.AuthorizedMinersState = new AuthorizedMinersState(
(Dictionary)blockChain.GetState(AuthorizedMinersState.Address)
);
if (policy is BlockPolicy bp)
{
bp.AuthorizedMinersState = new AuthorizedMinersState(
(Dictionary)blockChain.GetState(AuthorizedMinersState.Address)
);
}

await blockChain.MineBlock(stranger);

Expand All @@ -183,10 +187,114 @@ public async Task ValidateNextBlockWithAuthorizedMinersState()
await blockChain.MineBlock(stranger);
}

[Fact]
public async Task GetNextBlockDifficultyWithAuthorizedMinersState()
{
var adminPrivateKey = new PrivateKey();
var adminAddress = adminPrivateKey.ToAddress();
var miner = new PrivateKey().ToAddress();
var miners = new[] { miner };

var blockPolicySource = new BlockPolicySource(Logger.None);
IBlockPolicy<PolymorphicAction<ActionBase>> policy = blockPolicySource.GetPolicy(4096);
Block<PolymorphicAction<ActionBase>> genesis = MakeGenesisBlock(
adminAddress,
ImmutableHashSet<Address>.Empty,
new AuthorizedMinersState(miners, 2, 6)
);
using var store = new DefaultStore(null);
var blockChain = new BlockChain<PolymorphicAction<ActionBase>>(
policy,
store,
store,
genesis,
renderers: new[] { blockPolicySource.BlockRenderer }
);

if (policy is BlockPolicy bp)
{
bp.AuthorizedMinersState = new AuthorizedMinersState(
(Dictionary)blockChain.GetState(AuthorizedMinersState.Address)
);
}

var dateTimeOffset = DateTimeOffset.MinValue;

// Index 1
Assert.Equal(4096, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 2, target index
Assert.Equal(4096, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 3
Assert.Equal(4096, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 4, target index
Assert.Equal(4096, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 5
Assert.Equal(4096, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 6, target index
Assert.Equal(4096, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 7
Assert.Equal(4098, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 8
Assert.Equal(4100, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 9
Assert.Equal(4102, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 10
Assert.Equal(4104, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(1);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 11
Assert.Equal(4106, policy.GetNextBlockDifficulty(blockChain));

dateTimeOffset += TimeSpan.FromSeconds(20);
await blockChain.MineBlock(miner, dateTimeOffset);

// Index 12
Assert.Equal(4104, policy.GetNextBlockDifficulty(blockChain));
}

private Block<PolymorphicAction<ActionBase>> MakeGenesisBlock(
Address adminAddress,
IImmutableSet<Address> activatedAddresses,
AuthorizedMinersState authorizedMinersState = null
AuthorizedMinersState authorizedMinersState = null,
DateTimeOffset? timestamp = null
)
{
var nonce = new byte[] { 0x00, 0x01, 0x02, 0x03 };
Expand Down Expand Up @@ -216,7 +324,8 @@ public async Task ValidateNextBlockWithAuthorizedMinersState()
PendingActivationStates = new[] { pendingActivation },
AuthorizedMinersState = authorizedMinersState,
},
}
},
timestamp: timestamp ?? DateTimeOffset.MinValue
);
}
}
Expand Down
130 changes: 106 additions & 24 deletions Lib9c/BlockPolicy.cs
@@ -1,50 +1,132 @@
using Lib9c;
using Libplanet;
using Libplanet.Action;
using Libplanet.Blockchain;
using Libplanet.Blockchain.Policies;
using Libplanet.Blocks;
using Libplanet.Tx;
using Nekoyume.Action;
using System;
using Lib9c;
using Libplanet;
using Nekoyume.Model.State;
using NCAction = Libplanet.Action.PolymorphicAction<Nekoyume.Action.ActionBase>;

namespace Nekoyume.BlockChain
{
public class BlockPolicy : IBlockPolicy<NCAction>
public class BlockPolicy : BlockPolicy<NCAction>
{
private readonly IBlockPolicy<NCAction> _impl;

private readonly Func<Block<NCAction>, InvalidBlockException> _blockValidator;
private readonly long _minimumDifficulty;
private readonly long _difficultyBoundDivisor;

public BlockPolicy(
IBlockPolicy<NCAction> impl,
Func<Block<NCAction>, InvalidBlockException> blockValidator
)
IAction blockAction,
TimeSpan blockInterval,
long minimumDifficulty,
int difficultyBoundDivisor,
Func<Transaction<NCAction>, BlockChain<NCAction>, bool> doesTransactionFollowPolicy = null
) : base(
blockAction,
blockInterval,
minimumDifficulty,
difficultyBoundDivisor,
doesTransactionFollowPolicy)
{
_impl = impl;
_blockValidator = blockValidator;
_minimumDifficulty = minimumDifficulty;
_difficultyBoundDivisor = difficultyBoundDivisor;
}

public IAction BlockAction => _impl.BlockAction;
public AuthorizedMinersState AuthorizedMinersState { get; set; }

public bool DoesTransactionFollowsPolicy(
Transaction<NCAction> transaction,
BlockChain<NCAction> blockChain
) => _impl.DoesTransactionFollowsPolicy(transaction, blockChain);
public override InvalidBlockException ValidateNextBlock(BlockChain<NCAction> blocks, Block<NCAction> nextBlock)
{
InvalidBlockException e = ValidateMinerAuthority(nextBlock);
return e ?? base.ValidateNextBlock(blocks, nextBlock);
}

public long GetNextBlockDifficulty(BlockChain<NCAction> blocks)
=> _impl.GetNextBlockDifficulty(blocks);
public override long GetNextBlockDifficulty(BlockChain<NCAction> blocks)
{
if (AuthorizedMinersState is null)
{
return base.GetNextBlockDifficulty(blocks);
}

public InvalidBlockException ValidateNextBlock(BlockChain<NCAction> blocks, Block<NCAction> nextBlock)
long index = blocks.Count;

if (index < 0)
{
throw new InvalidBlockIndexException(
$"index must be 0 or more, but its index is {index}.");
}

if (index <= 1)
{
return index == 0 ? 0 : _minimumDifficulty;
}

var prevIndex = IsTargetBlock(index - 1) ? index - 2 : index - 1;
var beforePrevIndex = IsTargetBlock(prevIndex - 1) ? prevIndex - 2 : prevIndex - 1;

if (beforePrevIndex > AuthorizedMinersState.ValidUntil)
{
return base.GetNextBlockDifficulty(blocks);
}

if (IsTargetBlock(index) || prevIndex <= 1 || beforePrevIndex <= 1)
{
return _minimumDifficulty;
}

var prevBlock = blocks[prevIndex];
var beforePrevBlock = blocks[beforePrevIndex];

DateTimeOffset beforePrevTimestamp = beforePrevBlock.Timestamp;
DateTimeOffset prevTimestamp = prevBlock.Timestamp;
TimeSpan timeDiff = prevTimestamp - beforePrevTimestamp;
long timeDiffMilliseconds = (long)timeDiff.TotalMilliseconds;
const long minimumMultiplier = -99;
long multiplier = 1 - timeDiffMilliseconds / (long)BlockInterval.TotalMilliseconds;
multiplier = Math.Max(multiplier, minimumMultiplier);

var prevDifficulty = prevBlock.Difficulty;
var offset = prevDifficulty / _difficultyBoundDivisor;
long nextDifficulty = prevDifficulty + (offset * multiplier);

return Math.Max(nextDifficulty, _minimumDifficulty);
}

private InvalidBlockException ValidateMinerAuthority(Block<NCAction> block)
{
InvalidBlockException excFromValidator = _blockValidator(nextBlock);
if (!(excFromValidator is null))
if (AuthorizedMinersState is null)
{
return null;
}

if (!(block.Miner is Address miner))
{
return excFromValidator;
return null;
}

return _impl.ValidateNextBlock(blocks, nextBlock);
if (!IsTargetBlock(block.Index))
{
return null;
}

bool minedByAuthorities = AuthorizedMinersState.Miners.Contains(miner);

if (minedByAuthorities)
{
return null;
}

return new InvalidMinerException(
$"The given block[{block}] isn't mined by authorities.",
miner
);
}

private bool IsTargetBlock(long blockIndex)
{
return blockIndex > 0
&& blockIndex <= AuthorizedMinersState.ValidUntil
&& blockIndex % AuthorizedMinersState.Interval == 0;
}
}
}
51 changes: 9 additions & 42 deletions Lib9c/BlockPolicySource.cs
Expand Up @@ -46,23 +46,18 @@ public BlockPolicySource(ILogger logger, LogEventLevel logEventLevel = LogEventL
new LoggedRenderer<NCAction>(BlockRenderer, logger, logEventLevel);
}

public AuthorizedMinersState AuthorizedMinersState { get; set; }

// FIXME 남은 설정들도 설정화 해야 할지도?
public IBlockPolicy<NCAction> GetPolicy(int minimumDifficulty)
{
#if UNITY_EDITOR
return new DebugPolicy();
#else
return new BlockPolicy(
new BlockPolicy<NCAction>(
new RewardGold(),
_blockInterval,
minimumDifficulty,
2048,
doesTransactionFollowPolicy: IsSignerAuthorized
),
ValidateBlock
new RewardGold(),
_blockInterval,
minimumDifficulty,
2048,
IsSignerAuthorized
);
#endif
}
Expand All @@ -75,51 +70,23 @@ public IBlockPolicy<NCAction> GetPolicy(int minimumDifficulty)
BlockChain<NCAction> blockChain
)
{
if (transaction.Actions.Count == 1 &&
if (transaction.Actions.Count == 1 &&
transaction.Actions.First().InnerAction is ActivateAccount)
{
return true;
}

if (blockChain.GetState(ActivatedAccountsState.Address) is Dictionary asDict)
{
IImmutableSet<Address> activatedAccounts =
IImmutableSet<Address> activatedAccounts =
new ActivatedAccountsState(asDict).Accounts;
return !activatedAccounts.Any() ||
return !activatedAccounts.Any() ||
activatedAccounts.Contains(transaction.Signer);
}
else
{
return true;
}
}

private InvalidBlockException ValidateBlock(Block<NCAction> block)
{
if (AuthorizedMinersState is null)
{
return null;
}

if (!(block.Miner is Address miner))
{
return null;
}

bool targetBlock =
0 < block.Index && block.Index <= AuthorizedMinersState.ValidUntil &&
block.Index % AuthorizedMinersState.Interval == 0;
bool minedByAuthorities = AuthorizedMinersState.Miners.Contains(miner);

if (targetBlock && !minedByAuthorities)
{
return new InvalidMinerException(
$"The given block[{block}] isn't mined by authorities.",
miner
);
}

return null;
}
}
}

0 comments on commit b11ab6b

Please sign in to comment.