diff --git a/neo.UnitTests/UT_Consensus.cs b/neo.UnitTests/UT_Consensus.cs index d3faa858d0..741241978d 100644 --- a/neo.UnitTests/UT_Consensus.cs +++ b/neo.UnitTests/UT_Consensus.cs @@ -14,7 +14,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using System.Security.Cryptography; using ECPoint = Neo.Cryptography.ECC.ECPoint; @@ -179,20 +178,20 @@ public void TestSerializeAndDeserializeConsensusContext() { var consensusContext = new ConsensusContext(null); consensusContext.State = ConsensusState.CommitSent; - consensusContext.PrevHash = UInt256.Parse("3333333377777777333333337777777733333333777777773333333377777777"); - consensusContext.BlockIndex = 1337; + consensusContext.PrevHash = UInt256.Parse("0xd42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"); + consensusContext.BlockIndex = 1; consensusContext.ViewNumber = 2; consensusContext.Validators = new ECPoint[7] { - TestUtils.StandbyValidators[0], - ECPoint.Multiply(TestUtils.StandbyValidators[0], new BigInteger(2)), - ECPoint.Multiply(TestUtils.StandbyValidators[0], new BigInteger(3)), - ECPoint.Multiply(TestUtils.StandbyValidators[0], new BigInteger(4)), - ECPoint.Multiply(TestUtils.StandbyValidators[0], new BigInteger(5)), - ECPoint.Multiply(TestUtils.StandbyValidators[0], new BigInteger(6)), - ECPoint.Multiply(TestUtils.StandbyValidators[0], new BigInteger(7)), + ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", Cryptography.ECC.ECCurve.Secp256r1) }; - consensusContext.MyIndex = 3; + consensusContext.MyIndex = -1; consensusContext.PrimaryIndex = 6; consensusContext.Timestamp = 4244941711; consensusContext.Nonce = UInt64.MaxValue; diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index dd1257f79a..626a5b2931 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -17,9 +17,8 @@ namespace Neo.Consensus internal class ConsensusContext : IConsensusContext { public const uint Version = 0; - public ConsensusState State { get; set; } - public UInt256 PrevHash { get; set; } public uint BlockIndex { get; set; } + public UInt256 PrevHash { get; set; } public byte ViewNumber { get; set; } public ECPoint[] Validators { get; set; } public int MyIndex { get; set; } @@ -33,6 +32,7 @@ internal class ConsensusContext : IConsensusContext public ConsensusPayload[] CommitPayloads { get; set; } public ConsensusPayload[] ChangeViewPayloads { get; set; } public ConsensusPayload[] LastChangeViewPayloads { get; set; } + public ConsensusState State { get; set; } public Snapshot Snapshot { get; private set; } private KeyPair keyPair; private readonly Wallet wallet; @@ -66,13 +66,10 @@ public Block CreateBlock() public void Deserialize(BinaryReader reader) { - if (reader.ReadUInt32() != Version) return; - State = (ConsensusState)reader.ReadByte(); - PrevHash = reader.ReadSerializable(); - BlockIndex = reader.ReadUInt32(); + Reset(0); + if (reader.ReadUInt32() != Version) throw new FormatException(); + if (reader.ReadUInt32() != BlockIndex) throw new InvalidOperationException(); ViewNumber = reader.ReadByte(); - Validators = reader.ReadSerializableArray(); - MyIndex = reader.ReadInt32(); PrimaryIndex = reader.ReadUInt32(); Timestamp = reader.ReadUInt32(); Nonce = reader.ReadUInt64(); @@ -105,6 +102,7 @@ public void Deserialize(BinaryReader reader) LastChangeViewPayloads = new ConsensusPayload[reader.ReadVarInt()]; for (int i = 0; i < LastChangeViewPayloads.Length; i++) LastChangeViewPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; + State = (ConsensusState)reader.ReadByte(); } public void Dispose() @@ -251,7 +249,7 @@ public void Reset(byte viewNumber) keyPair = null; for (int i = 0; i < Validators.Length; i++) { - WalletAccount account = wallet.GetAccount(Validators[i]); + WalletAccount account = wallet?.GetAccount(Validators[i]); if (account?.HasKey != true) continue; MyIndex = i; keyPair = account.GetKey(); @@ -279,12 +277,8 @@ public void Reset(byte viewNumber) public void Serialize(BinaryWriter writer) { writer.Write(Version); - writer.Write((byte)State); - writer.Write(PrevHash); writer.Write(BlockIndex); writer.Write(ViewNumber); - writer.Write(Validators); - writer.Write(MyIndex); writer.Write(PrimaryIndex); writer.Write(Timestamp); writer.Write(Nonce); @@ -323,6 +317,7 @@ public void Serialize(BinaryWriter writer) if (!hasPayload) continue; writer.Write(payload); } + writer.Write((byte)State); } public void Fill() diff --git a/neo/Consensus/ConsensusService.cs b/neo/Consensus/ConsensusService.cs index 9ec6f982bb..50c0dbfcd6 100644 --- a/neo/Consensus/ConsensusService.cs +++ b/neo/Consensus/ConsensusService.cs @@ -11,7 +11,6 @@ using Neo.Wallets; using System; using System.Collections.Generic; -using System.IO; using System.Linq; namespace Neo.Consensus @@ -22,8 +21,6 @@ public class Start { public bool IgnoreRecoveryLogs; } public class SetViewNumber { public byte ViewNumber; } internal class Timer { public uint Height; public byte ViewNumber; } - private const byte ContextSerializationPrefix = 0xf4; - private readonly IConsensusContext context; private readonly IActorRef localNode; private readonly IActorRef taskManager; @@ -128,7 +125,7 @@ private void CheckPreparations() ConsensusPayload payload = context.MakeCommit(); Log($"send commit"); context.State |= ConsensusState.CommitSent; - store.Put(ContextSerializationPrefix, new byte[0], context.ToArray()); + context.Save(store); localNode.Tell(new LocalNode.SendDirectly { Inventory = payload }); // Set timer, so we will resend the commit in case of a networking issue ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock)); @@ -433,27 +430,17 @@ private void OnStart(Start options) { Log("OnStart"); started = true; - if (!options.IgnoreRecoveryLogs) + bool loadedState = !options.IgnoreRecoveryLogs && context.Load(store); + if (loadedState && context.State.HasFlag(ConsensusState.CommitSent)) { - byte[] data = store.Get(ContextSerializationPrefix, new byte[0]); - if (data != null) - { - using (MemoryStream ms = new MemoryStream(data, false)) - using (BinaryReader reader = new BinaryReader(ms)) - { - context.Deserialize(reader); - } - } - } - if (context.State.HasFlag(ConsensusState.CommitSent) && context.BlockIndex == Blockchain.Singleton.Height + 1) CheckPreparations(); - else - { - InitializeConsensus(0); - // Issue a ChangeView with NewViewNumber of 0 to request recovery messages on start-up. - if (context.BlockIndex == Blockchain.Singleton.HeaderHeight + 1) - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeChangeView(0) }); + return; } + + InitializeConsensus(0); + // Issue a ChangeView with NewViewNumber of 0 to request recovery messages on start-up. + if (context.BlockIndex == Blockchain.Singleton.HeaderHeight + 1) + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeChangeView(0) }); } private void OnTimer(Timer timer) diff --git a/neo/Consensus/Helper.cs b/neo/Consensus/Helper.cs new file mode 100644 index 0000000000..9a5df8fe9f --- /dev/null +++ b/neo/Consensus/Helper.cs @@ -0,0 +1,38 @@ +using Neo.IO; +using Neo.Persistence; +using System.IO; + +namespace Neo.Consensus +{ + internal static class Helper + { + /// + /// Prefix for saving consensus state. + /// + public const byte CN_Context = 0xf4; + + public static void Save(this IConsensusContext context, Store store) + { + store.PutSync(CN_Context, new byte[0], context.ToArray()); + } + + public static bool Load(this IConsensusContext context, Store store) + { + byte[] data = store.Get(CN_Context, new byte[0]); + if (data is null) return false; + using (MemoryStream ms = new MemoryStream(data, false)) + using (BinaryReader reader = new BinaryReader(ms)) + { + try + { + context.Deserialize(reader); + } + catch + { + return false; + } + return true; + } + } + } +} diff --git a/neo/Persistence/LevelDB/LevelDBStore.cs b/neo/Persistence/LevelDB/LevelDBStore.cs index c4cb97437e..b74034f1ca 100644 --- a/neo/Persistence/LevelDB/LevelDBStore.cs +++ b/neo/Persistence/LevelDB/LevelDBStore.cs @@ -116,5 +116,10 @@ public override void Put(byte prefix, byte[] key, byte[] value) { db.Put(WriteOptions.Default, SliceBuilder.Begin(prefix).Add(key), value); } + + public override void PutSync(byte prefix, byte[] key, byte[] value) + { + db.Put(new WriteOptions { Sync = true }, SliceBuilder.Begin(prefix).Add(key), value); + } } } diff --git a/neo/Persistence/LevelDB/Prefixes.cs b/neo/Persistence/LevelDB/Prefixes.cs index a92363f38f..566f9bf431 100644 --- a/neo/Persistence/LevelDB/Prefixes.cs +++ b/neo/Persistence/LevelDB/Prefixes.cs @@ -19,5 +19,10 @@ internal static class Prefixes public const byte IX_CurrentHeader = 0xc1; public const byte SYS_Version = 0xf0; + + /* Prefixes 0xf1 to 0xff are reserved for external use. + * + * Note: The saved consensus state uses the Prefix 0xf4 + */ } } diff --git a/neo/Persistence/Store.cs b/neo/Persistence/Store.cs index ea1e1e705c..fd7e8b381b 100644 --- a/neo/Persistence/Store.cs +++ b/neo/Persistence/Store.cs @@ -36,6 +36,7 @@ public abstract class Store : IPersistence public abstract MetaDataCache GetBlockHashIndex(); public abstract MetaDataCache GetHeaderHashIndex(); public abstract void Put(byte prefix, byte[] key, byte[] value); + public abstract void PutSync(byte prefix, byte[] key, byte[] value); public abstract Snapshot GetSnapshot(); }