public async Task IgnoreExistingBlocks() { var keyA = new PrivateKey(); var keyB = new PrivateKey(); Swarm <DumbAction> swarmA = CreateSwarm(keyA); Swarm <DumbAction> swarmB = CreateSwarm(keyB); BlockChain <DumbAction> chainA = swarmA.BlockChain; BlockChain <DumbAction> chainB = swarmB.BlockChain; Block <DumbAction> genesis = await chainA.MineBlock(keyA); chainB.Append(genesis); foreach (int i in Enumerable.Range(0, 3)) { await chainA.MineBlock(keyA); } try { await StartAsync(swarmA); await StartAsync(swarmB); await BootstrapAsync(swarmB, swarmA.AsPeer); swarmA.BroadcastBlock(chainA[-1]); await swarmB.BlockAppended.WaitAsync(); Assert.Equal(chainA.BlockHashes, chainB.BlockHashes); CancellationTokenSource cts = new CancellationTokenSource(); swarmA.BroadcastBlock(chainA[-1]); Task t = swarmB.BlockAppended.WaitAsync(cts.Token); // Actually, previous code may pass this test if message is // delayed over 5 seconds. await Task.Delay(5000); Assert.False(t.IsCompleted); cts.Cancel(); } finally { await StopAsync(swarmA); await StopAsync(swarmB); } }
public async Task MineBlockAsync(CancellationToken cancellationToken) { var txs = new HashSet <Transaction <PolymorphicAction <ActionBase> > >(); var timeStamp = DateTimeOffset.UtcNow; var prevTimeStamp = _chain?.Tip?.Timestamp; // FIXME 년도가 바뀌면 깨지는 계산 방식. 테스트 끝나면 변경해야함 // 하루 한번 보상을 제공 // TODO: Move ranking reward logic and condition into block action. if (prevTimeStamp is DateTimeOffset t && timeStamp.DayOfYear - t.DayOfYear == 1) { var rankingRewardTx = RankingReward(); txs.Add(rankingRewardTx); } var invalidTxs = txs; try { var block = await _chain.MineBlock(Address, DateTimeOffset.UtcNow, cancellationToken : cancellationToken); if (_swarm.Running) { _swarm.BroadcastBlock(block); } } catch (OperationCanceledException) { Log.Debug("Mining was canceled due to change of tip."); } catch (InvalidTxException invalidTxException) { var invalidTx = _chain.GetTransaction(invalidTxException.TxId); Log.Debug($"Tx[{invalidTxException.TxId}] is invalid. mark to unstage."); invalidTxs.Add(invalidTx); } catch (UnexpectedlyTerminatedActionException actionException) { if (actionException.TxId is TxId txId) { Log.Debug( $"Tx[{actionException.TxId}]'s action is invalid. mark to unstage. {actionException}"); invalidTxs.Add(_chain.GetTransaction(txId)); } } catch (Exception ex) { Log.Error(ex, $"exception was thrown. {ex}"); } finally { foreach (var invalidTx in invalidTxs) { _chain.UnstageTransaction(invalidTx); } } }
public async Task BroadcastBlockToReconnectedPeer() { var miner = new PrivateKey(); Swarm <DumbAction> seed = CreateSwarm(miner); BlockChain <DumbAction> chainWithBlocks = seed.BlockChain; var privateKey = new PrivateKey(); Swarm <DumbAction> swarmA = CreateSwarm(privateKey: privateKey); Swarm <DumbAction> swarmB = CreateSwarm(privateKey: privateKey); foreach (int i in Enumerable.Range(0, 10)) { await chainWithBlocks.MineBlock(miner); } try { await StartAsync(seed); await StartAsync(swarmA); await StartAsync(swarmB); Assert.Equal(swarmA.AsPeer, swarmB.AsPeer); await swarmA.AddPeersAsync(new[] { seed.AsPeer }, null); await StopAsync(swarmA); await seed.PeerDiscovery.RefreshTableAsync( TimeSpan.Zero, default(CancellationToken)); Assert.DoesNotContain(swarmA.AsPeer, seed.Peers); await swarmB.AddPeersAsync(new[] { seed.AsPeer }, null); // This is added for context switching. await Task.Delay(100); Assert.Contains(swarmB.AsPeer, seed.Peers); Assert.Contains(seed.AsPeer, swarmB.Peers); seed.BroadcastBlock(chainWithBlocks.Tip); await swarmB.BlockAppended.WaitAsync(); Assert.NotEqual(chainWithBlocks.BlockHashes, swarmA.BlockChain.BlockHashes); Assert.Equal(chainWithBlocks.BlockHashes, swarmB.BlockChain.BlockHashes); } finally { await StopAsync(seed); await StopAsync(swarmA); await StopAsync(swarmB); seed.Dispose(); swarmA.Dispose(); swarmB.Dispose(); } }
public async Task <Block <PolymorphicAction <ActionBase> > > MineBlockAsync( int maxTransactions, CancellationToken cancellationToken) { var txs = new HashSet <Transaction <PolymorphicAction <ActionBase> > >(); var invalidTxs = txs; Block <PolymorphicAction <ActionBase> > block = null; try { block = await _chain.MineBlock( Address, DateTimeOffset.UtcNow, cancellationToken : cancellationToken, maxTransactions : maxTransactions); if (_swarm.Running) { _swarm.BroadcastBlock(block); } } catch (OperationCanceledException) { Log.Debug("Mining was canceled due to change of tip."); } catch (InvalidTxException invalidTxException) { var invalidTx = _chain.GetTransaction(invalidTxException.TxId); Log.Debug($"Tx[{invalidTxException.TxId}] is invalid. mark to unstage."); invalidTxs.Add(invalidTx); } catch (UnexpectedlyTerminatedActionException actionException) { if (actionException.TxId is TxId txId) { Log.Debug( $"Tx[{actionException.TxId}]'s action is invalid. mark to unstage. {actionException}"); invalidTxs.Add(_chain.GetTransaction(txId)); } } catch (Exception ex) { Log.Error(ex, $"exception was thrown. {ex}"); } finally { #pragma warning disable LAA1002 foreach (var invalidTx in invalidTxs) #pragma warning restore LAA1002 { _chain.UnstageTransaction(invalidTx); } } return(block); }
public async Task BroadcastBlockWithoutGenesis() { var keyA = new PrivateKey(); var keyB = new PrivateKey(); Swarm <DumbAction> swarmA = CreateSwarm(keyA); Swarm <DumbAction> swarmB = CreateSwarm(keyB); BlockChain <DumbAction> chainA = swarmA.BlockChain; BlockChain <DumbAction> chainB = swarmB.BlockChain; try { await StartAsync(swarmA); await StartAsync(swarmB); await BootstrapAsync(swarmB, swarmA.AsPeer); await chainA.MineBlock(keyA); swarmA.BroadcastBlock(chainA[-1]); await swarmB.BlockAppended.WaitAsync(); Assert.Equal(chainB.BlockHashes, chainA.BlockHashes); await chainA.MineBlock(keyB); swarmA.BroadcastBlock(chainA[-1]); await swarmB.BlockAppended.WaitAsync(); Assert.Equal(chainB.BlockHashes, chainA.BlockHashes); } finally { await StopAsync(swarmA); await StopAsync(swarmB); } }
public async Task BroadcastIgnoreFromDifferentGenesisHash() { var receiverKey = new PrivateKey(); Swarm <DumbAction> receiverSwarm = CreateSwarm(receiverKey); BlockChain <DumbAction> receiverChain = receiverSwarm.BlockChain; var seedStateStore = new TrieStateStore(new MemoryKeyValueStore()); IBlockPolicy <DumbAction> policy = receiverChain.Policy; Block <DumbAction> mismatchedGenesis = new BlockContent <DumbAction> { PublicKey = receiverKey.PublicKey, Timestamp = DateTimeOffset.MinValue, } .Mine(policy.GetHashAlgorithm(0)) .Evaluate(receiverKey, policy.BlockAction, seedStateStore); BlockChain <DumbAction> seedChain = TestUtils.MakeBlockChain( policy, new DefaultStore(path: null), seedStateStore, genesisBlock: mismatchedGenesis); var seedMiner = new PrivateKey(); Swarm <DumbAction> seedSwarm = CreateSwarm(seedChain, seedMiner); try { await StartAsync(receiverSwarm); await StartAsync(seedSwarm); await receiverSwarm.AddPeersAsync(new[] { seedSwarm.AsPeer }, null); Block <DumbAction> block = await seedChain.MineBlock(seedMiner); seedSwarm.BroadcastBlock(block); while (!((NetMQTransport)receiverSwarm.Transport).MessageHistory .Any(msg => msg is BlockHeaderMessage)) { await Task.Delay(100); } await Task.Delay(100); Assert.NotEqual(seedChain.Tip, receiverChain.Tip); } finally { await StopAsync(seedSwarm); await StopAsync(receiverSwarm); seedSwarm.Dispose(); } }
public async Task BroadcastIgnoreFromDifferentGenesisHash() { Swarm <DumbAction> receiverSwarm = CreateSwarm(); BlockChain <DumbAction> receiverChain = receiverSwarm.BlockChain; var invalidGenesisBlock = new Block <DumbAction>( 0, 0, 0, new Nonce(new byte[] { 0x10, 0x00, 0x00, 0x00 }), receiverSwarm.Address, null, DateTimeOffset.MinValue, Enumerable.Empty <Transaction <DumbAction> >()); BlockChain <DumbAction> seedChain = TestUtils.MakeBlockChain( receiverChain.Policy, new DefaultStore(path: null), new TrieStateStore(new MemoryKeyValueStore(), new MemoryKeyValueStore()), genesisBlock: invalidGenesisBlock); Swarm <DumbAction> seedSwarm = CreateSwarm(seedChain); try { await StartAsync(receiverSwarm); await StartAsync(seedSwarm); await receiverSwarm.AddPeersAsync(new[] { seedSwarm.AsPeer }, null); Block <DumbAction> block = await seedChain.MineBlock(seedSwarm.Address); seedSwarm.BroadcastBlock(block); while (!((NetMQTransport)receiverSwarm.Transport).MessageHistory .Any(msg => msg is BlockHeaderMessage)) { await Task.Delay(100); } await Task.Delay(100); Assert.NotEqual(seedChain.Tip, receiverChain.Tip); } finally { await StopAsync(seedSwarm); await StopAsync(receiverSwarm); seedSwarm.Dispose(); } }
public async Task DetermineCanonicalChain(short canonComparerType) { IComparer <IBlockExcerpt> canonComparer; switch (canonComparerType) { default: canonComparer = new TotalDifficultyComparer(); break; case 1: canonComparer = new AnonymousComparer <IBlockExcerpt>((a, b) => string.Compare( a.Hash.ToString(), b.Hash.ToString(), StringComparison.Ordinal ) ); break; } var policy = new BlockPolicy <DumbAction>( new MinerReward(1), canonicalChainComparer: canonComparer ); BlockChain <DumbAction> chain1 = TestUtils.MakeBlockChain( policy, new DefaultStore(null), new TrieStateStore(new MemoryKeyValueStore()) ); BlockChain <DumbAction> chain2 = TestUtils.MakeBlockChain( policy, new DefaultStore(null), new TrieStateStore(new MemoryKeyValueStore()) ); var key1 = new PrivateKey(); var key2 = new PrivateKey(); Swarm <DumbAction> miner1 = CreateSwarm(chain1, key1); Swarm <DumbAction> miner2 = CreateSwarm(chain2, key2); await chain1.MineBlock(key1); await chain1.MineBlock(key2); Block <DumbAction> bestBlock; switch (canonComparerType) { default: long nextDifficulty = (long)chain1.Tip.TotalDifficulty + policy.GetNextBlockDifficulty(chain2); bestBlock = TestUtils.MineNext( chain2.Tip, policy.GetHashAlgorithm, difficulty: nextDifficulty, blockInterval: TimeSpan.FromMilliseconds(1), miner: TestUtils.ChainPrivateKey.PublicKey ).Evaluate(TestUtils.ChainPrivateKey, chain2); _output.WriteLine("chain1's total difficulty: {0}", chain1.Tip.TotalDifficulty); _output.WriteLine("chain2's total difficulty: {0}", bestBlock.TotalDifficulty); break; case 1: string chain1TipHash = chain1.Tip.Hash.ToString(); string hashStr; do { bestBlock = TestUtils.MineNext( chain2.Tip, policy.GetHashAlgorithm, difficulty: policy.GetNextBlockDifficulty(chain2), blockInterval: TimeSpan.FromMilliseconds(1), miner: TestUtils.ChainPrivateKey.PublicKey ).Evaluate(TestUtils.ChainPrivateKey, chain2); hashStr = bestBlock.Hash.ToString(); _output.WriteLine("chain1's tip hash: {0}", chain1.Tip.Hash); _output.WriteLine("chain2's tip hash: {0}", bestBlock.Hash); _output.WriteLine(string.Empty); }while (string.Compare(chain1TipHash, hashStr, StringComparison.Ordinal) >= 0); break; } Assert.True( canonComparer.Compare( new BlockPerception(bestBlock), chain1.PerceiveBlock(chain1.Tip) ) > 0 ); chain2.Append(bestBlock); try { await StartAsync(miner1); await StartAsync(miner2); await BootstrapAsync(miner2, miner1.AsPeer); miner2.BroadcastBlock(bestBlock); _output.WriteLine("miner1 is waiting for a new block..."); await miner1.BlockReceived.WaitAsync(); Assert.Equal(miner1.BlockChain.Tip, bestBlock); Assert.Equal(miner2.BlockChain.Tip, bestBlock); } finally { await StopAsync(miner1); await StopAsync(miner2); miner1.Dispose(); miner2.Dispose(); } }
MineBlockAsync(CancellationToken cancellationToken) { var txs = new HashSet <Transaction <PolymorphicAction <ActionBase> > >(); var invalidTxs = txs; Block <PolymorphicAction <ActionBase> > mainBlock = null; Block <PolymorphicAction <ActionBase> > subBlock = null; try { mainBlock = await _mainChain.MineBlock( _privateKey, DateTimeOffset.UtcNow, cancellationToken : cancellationToken); subBlock = await _subChain.MineBlock( _privateKey, DateTimeOffset.UtcNow, cancellationToken : cancellationToken); if (_reorgInterval != 0 && subBlock.Index % _reorgInterval == 0) { Log.Debug("Force reorg!"); _subSwarm.BroadcastBlock(subBlock); } if (_mainSwarm.Running) { _mainSwarm.BroadcastBlock(mainBlock); } } catch (OperationCanceledException) { Log.Debug("Mining was canceled due to change of tip."); } catch (InvalidTxException invalidTxException) { var invalidTx = _mainChain.GetTransaction(invalidTxException.TxId); Log.Debug($"Tx[{invalidTxException.TxId}] is invalid. mark to unstage."); invalidTxs.Add(invalidTx); } catch (UnexpectedlyTerminatedActionException actionException) { if (actionException.TxId is TxId txId) { Log.Debug( $"Tx[{actionException.TxId}]'s action is invalid. mark to unstage. {actionException}"); invalidTxs.Add(_mainChain.GetTransaction(txId)); } } catch (Exception ex) { Log.Error(ex, $"exception was thrown. {ex}"); } finally { #pragma warning disable LAA1002 foreach (var invalidTx in invalidTxs) #pragma warning restore LAA1002 { _mainChain.UnstageTransaction(invalidTx); _subChain.UnstageTransaction(invalidTx); } } return(mainBlock, subBlock); }
private IEnumerator CoMiner() { while (true) { var txs = new HashSet <Transaction <PolymorphicAction <ActionBase> > >(); var task = Task.Run(async() => { var block = await _blocks.MineBlock(Address); if (_swarm?.Running ?? false) { _swarm.BroadcastBlock(block); } return(block); }); yield return(new WaitUntil(() => task.IsCompleted)); if (!task.IsCanceled && !task.IsFaulted) { var block = task.Result; Debug.Log($"created block index: {block.Index}, difficulty: {block.Difficulty}"); } else { var invalidTxs = txs; var retryActions = new HashSet <IImmutableList <PolymorphicAction <ActionBase> > >(); if (task.IsFaulted) { foreach (var ex in task.Exception.InnerExceptions) { if (ex is InvalidTxNonceException invalidTxNonceException) { var invalidNonceTx = _store.GetTransaction <PolymorphicAction <ActionBase> >(invalidTxNonceException.TxId); if (invalidNonceTx.Signer == Address) { Debug.Log($"Tx[{invalidTxNonceException.TxId}] nonce is invalid. Retry it."); retryActions.Add(invalidNonceTx.Actions); } } if (ex is InvalidTxException invalidTxException) { Debug.Log($"Tx[{invalidTxException.TxId}] is invalid. mark to unstage."); invalidTxs.Add(_store.GetTransaction <PolymorphicAction <ActionBase> >(invalidTxException.TxId)); } Debug.LogException(ex); } } _blocks.UnstageTransactions(invalidTxs); foreach (var retryAction in retryActions) { MakeTransaction(retryAction, true); } } } }
public async Task BroadcastBlockToReconnectedPeer() { var miner = new PrivateKey(); var policy = new NullBlockPolicy <DumbAction>(); var fx = new MemoryStoreFixture(policy.BlockAction); var minerChain = MakeBlockChain(policy, fx.Store, fx.StateStore); foreach (int i in Enumerable.Range(0, 10)) { await minerChain.MineBlock(miner); } Swarm <DumbAction> seed = CreateSwarm( miner, policy: policy, genesis: minerChain.Genesis ); BlockChain <DumbAction> seedChain = seed.BlockChain; var privateKey = new PrivateKey(); Swarm <DumbAction> swarmA = CreateSwarm( privateKey: privateKey, policy: policy, genesis: minerChain.Genesis ); Swarm <DumbAction> swarmB = CreateSwarm( privateKey: privateKey, policy: policy, genesis: minerChain.Genesis ); foreach (BlockHash blockHash in minerChain.BlockHashes.Skip(1).Take(4)) { seedChain.Append(minerChain[blockHash]); } try { await StartAsync(seed); await StartAsync(swarmA); await StartAsync(swarmB); Assert.Equal(swarmA.AsPeer, swarmB.AsPeer); await swarmA.AddPeersAsync(new[] { seed.AsPeer }, null); await StopAsync(swarmA); await seed.PeerDiscovery.RefreshTableAsync( TimeSpan.Zero, default(CancellationToken)); Assert.DoesNotContain(swarmA.AsPeer, seed.Peers); foreach (BlockHash blockHash in minerChain.BlockHashes.Skip(5)) { seedChain.Append(minerChain[blockHash]); } await swarmB.AddPeersAsync(new[] { seed.AsPeer }, null); // This is added for context switching. await Task.Delay(100); Assert.Contains(swarmB.AsPeer, seed.Peers); Assert.Contains(seed.AsPeer, swarmB.Peers); seed.BroadcastBlock(seedChain.Tip); await swarmB.BlockAppended.WaitAsync(); Assert.NotEqual(seedChain.BlockHashes, swarmA.BlockChain.BlockHashes); Assert.Equal(seedChain.BlockHashes, swarmB.BlockChain.BlockHashes); } finally { await StopAsync(seed); await StopAsync(swarmA); await StopAsync(swarmB); seed.Dispose(); swarmA.Dispose(); swarmB.Dispose(); } }