diff --git a/src/Networks/Terracoin/Terracoin.Node/Program.cs b/src/Networks/Terracoin/Terracoin.Node/Program.cs new file mode 100644 index 000000000..7bd29e17b --- /dev/null +++ b/src/Networks/Terracoin/Terracoin.Node/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Blockcore; +using Blockcore.Builder; +using Blockcore.Configuration; +using Blockcore.Features.BlockStore; +using Blockcore.Features.ColdStaking; +using Blockcore.Features.Consensus; +using Blockcore.Features.Diagnostic; +using Blockcore.Features.MemoryPool; +using Blockcore.Features.Miner; +using Blockcore.Features.NodeHost; +using Blockcore.Features.RPC; +using Blockcore.Utilities; + +namespace Terracoin.Daemon +{ + public class Program + { + public static async Task Main(string[] args) + { + try + { + var nodeSettings = new NodeSettings(networksSelector: Networks.Networks.Terracoin, args: args); + + IFullNodeBuilder nodeBuilder = new FullNodeBuilder() + .UseNodeSettings(nodeSettings) + .UseBlockStore() + .UsePosConsensus() + .UseMempool() + .UseColdStakingWallet() + .AddPowPosMining() + .UseNodeHost() + .AddRPC() + .UseDiagnosticFeature(); + + IFullNode node = nodeBuilder.Build(); + + if (node != null) + await node.RunAsync(); + } + catch (Exception ex) + { + Console.WriteLine("There was a problem initializing the node. Details: '{0}'", ex); + } + } + } +} diff --git a/src/Networks/Terracoin/Terracoin.Node/Properties/launchSettings.json b/src/Networks/Terracoin/Terracoin.Node/Properties/launchSettings.json new file mode 100644 index 000000000..5957bfb1b --- /dev/null +++ b/src/Networks/Terracoin/Terracoin.Node/Properties/launchSettings.json @@ -0,0 +1,39 @@ +{ + "profiles": { + "TRC (MAIN)": { + "commandName": "Project" + }, + "TRC (TEST)": { + "commandName": "Project", + "commandLineArgs": "-testnet" + }, + "TRC (MAIN/RPC)": { + "commandName": "Project", + "commandLineArgs": "-server -rpcallowip=127.0.0.1 -rpcbind=127.0.0.1 -rpcpassword=rpcpassword -rpcuser=rpcuser" + }, + "TRC (TEST/RPC)": { + "commandName": "Project", + "commandLineArgs": "-server -rpcallowip=127.0.0.1 -rpcbind=127.0.0.1 -rpcpassword=rpcpassword -rpcuser=rpcuser -testnet" + }, + "TRC (MAIN/LOCAL/RPC)": { + "commandName": "Project", + "commandLineArgs": "-server -rpcallowip=127.0.0.1 -rpcbind=127.0.0.1 -rpcpassword=rpcpassword -rpcuser=rpcuser -datadir=nodedata" + }, + "TRC (TEST/LOCAL/RPC)": { + "commandName": "Project", + "commandLineArgs": "-server -rpcallowip=127.0.0.1 -rpcbind=127.0.0.1 -rpcpassword=rpcpassword -rpcuser=rpcuser -datadir=nodedata -testnet" + }, + "TRC (MAIN/LOCAL/RPC/DEFAULT)": { + "commandName": "Project", + "commandLineArgs": "-server -rpcallowip=127.0.0.1 -rpcbind=127.0.0.1 -rpcpassword=rpcpassword -rpcuser=rpcuser -datadir=nodedata -defaultwalletname=default -defaultwalletpassword=default -unlockdefaultwallet -server" + }, + "TRC (TEST/LOCAL/RPC/DEFAULT)": { + "commandName": "Project", + "commandLineArgs": "-server -rpcallowip=127.0.0.1 -rpcbind=127.0.0.1 -rpcpassword=rpcpassword -rpcuser=rpcuser -datadir=nodedata -defaultwalletname=default -defaultwalletpassword=default -unlockdefaultwallet -server -testnet" + }, + "TRC (TEST/NO ASSUME VALID)": { + "commandName": "Project", + "commandLineArgs": "-testnet -assumevalid=0 -checkpoints=false" + } + } +} diff --git a/src/Networks/Terracoin/Terracoin.Node/Terracoin.Node.csproj b/src/Networks/Terracoin/Terracoin.Node/Terracoin.Node.csproj new file mode 100644 index 000000000..14914cdf9 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin.Node/Terracoin.Node.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp3.1 + 3.1.0 + Terracoin.Node + Exe + Blockcore + + + + latest + false + false + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Networks/Terracoin/Terracoin.sln b/src/Networks/Terracoin/Terracoin.sln new file mode 100644 index 000000000..4358006f9 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29409.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{306E751B-E744-4141-82A8-8D1062CC39D2}") = "Terracoin.Node", "src\Terracoin.Node\Terracoin.Node.csproj", "{B3BDD0F5-A698-4C60-8373-5B97B5FA612E}" +EndProject +Project("{306E751B-E744-4141-82A8-8D1062CC39D2}") = "Terracoin", "src\Terracoin\Terracoin.csproj", "{EB933638-E641-4F96-9AD7-B57F0C63B5C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B3BDD0F5-A698-4C60-8373-5B97B5FA612E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3BDD0F5-A698-4C60-8373-5B97B5FA612E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3BDD0F5-A698-4C60-8373-5B97B5FA612E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3BDD0F5-A698-4C60-8373-5B97B5FA612E}.Release|Any CPU.Build.0 = Release|Any CPU + {EB933638-E641-4F96-9AD7-B57F0C63B5C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB933638-E641-4F96-9AD7-B57F0C63B5C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB933638-E641-4F96-9AD7-B57F0C63B5C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB933638-E641-4F96-9AD7-B57F0C63B5C9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E1B84079-3527-45DC-8585-F99BCBA75D57} + EndGlobalSection +EndGlobal diff --git a/src/Networks/Terracoin/Terracoin/Networks/Deployments/TerracoinBIP9Deployments.cs b/src/Networks/Terracoin/Terracoin/Networks/Deployments/TerracoinBIP9Deployments.cs new file mode 100644 index 000000000..b3832631e --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/Deployments/TerracoinBIP9Deployments.cs @@ -0,0 +1,58 @@ +using Blockcore.Base.Deployments; +using Blockcore.Consensus.ScriptInfo; +using Blockcore.Consensus.TransactionInfo; + +namespace Terracoin.Networks.Deployments +{ + /// + /// BIP9 deployments for the Blockcore sample coin network. + /// + public class TerracoinBIP9Deployments : BIP9DeploymentsArray + { + // The position of each deployment in the deployments array. Note that this is decoupled from the actual position of the flag bit for the deployment in the block version. + public const int TestDummy = 0; + public const int CSV = 1; + public const int Segwit = 2; + public const int ColdStaking = 3; + + // The number of deployments. + public const int NumberOfDeployments = 4; + + /// + /// Constructs the BIP9 deployments array. + /// + public TerracoinBIP9Deployments() : base(NumberOfDeployments) + { + } + + /// + /// Gets the deployment flags to set when the deployment activates. + /// + /// The deployment number. + /// The deployment flags. + public override BIP9DeploymentFlags GetFlags(int deployment) + { + var flags = new BIP9DeploymentFlags(); + + switch (deployment) + { + case ColdStaking: + flags.ScriptFlags |= ScriptVerify.CheckColdStakeVerify; + break; + + case CSV: + // Start enforcing BIP68 (sequence locks), BIP112 (CHECKSEQUENCEVERIFY) and BIP113 (Median Time Past) using versionbits logic. + flags.ScriptFlags = ScriptVerify.CheckSequenceVerify; + flags.LockTimeFlags = Transaction.LockTimeFlags.VerifySequence | Transaction.LockTimeFlags.MedianTimePast; + break; + + case Segwit: + // Start enforcing WITNESS rules using versionbits logic. + flags.ScriptFlags = ScriptVerify.Witness; + break; + } + + return flags; + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/Networks.cs b/src/Networks/Terracoin/Terracoin/Networks/Networks.cs new file mode 100644 index 000000000..95353befb --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/Networks.cs @@ -0,0 +1,15 @@ +using Blockcore.Networks; + +namespace Terracoin.Networks +{ + public static class Networks + { + public static NetworksSelector Terracoin + { + get + { + return new NetworksSelector(() => new TerracoinMain(), () => new TerracoinTest(), () => new TerracoinRegTest()); + } + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/Policies/TerracoinStandardScriptsRegistry.cs b/src/Networks/Terracoin/Terracoin/Networks/Policies/TerracoinStandardScriptsRegistry.cs new file mode 100644 index 000000000..896314d5e --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/Policies/TerracoinStandardScriptsRegistry.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using Blockcore.Consensus.ScriptInfo; +using Blockcore.Consensus.TransactionInfo; +using Blockcore.Networks; +using NBitcoin.BitcoinCore; + +namespace Terracoin.Networks.Policies +{ + /// + /// Blockcore sample coin-specific standard transaction definitions. + /// + public class TerracoinStandardScriptsRegistry : StandardScriptsRegistry + { + // See MAX_OP_RETURN_RELAY in stratisX, + public const int MaxOpReturnRelay = 40; + + // Need a network-specific version of the template list + private readonly List standardTemplates = new List + { + PayToPubkeyHashTemplate.Instance, + PayToPubkeyTemplate.Instance, + PayToScriptHashTemplate.Instance, + PayToMultiSigTemplate.Instance, + new TxNullDataTemplate(MaxOpReturnRelay), + PayToWitTemplate.Instance + }; + + public override List GetScriptTemplates => standardTemplates; + + public override void RegisterStandardScriptTemplate(ScriptTemplate scriptTemplate) + { + if (!standardTemplates.Any(template => (template.Type == scriptTemplate.Type))) + { + standardTemplates.Add(scriptTemplate); + } + } + + public override bool IsStandardTransaction(Transaction tx, Network network) + { + return base.IsStandardTransaction(tx, network); + } + + public override bool AreOutputsStandard(Network network, Transaction tx) + { + return base.AreOutputsStandard(network, tx); + } + + public override ScriptTemplate GetTemplateFromScriptPubKey(Script script) + { + return standardTemplates.FirstOrDefault(t => t.CheckScriptPubKey(script)); + } + + public override bool IsStandardScriptPubKey(Network network, Script scriptPubKey) + { + return base.IsStandardScriptPubKey(network, scriptPubKey); + } + + public override bool AreInputsStandard(Network network, Transaction tx, CoinsView coinsView) + { + return base.AreInputsStandard(network, tx, coinsView); + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/Rules/TerracoinHeaderVersionRule.cs b/src/Networks/Terracoin/Terracoin/Networks/Rules/TerracoinHeaderVersionRule.cs new file mode 100644 index 000000000..34fb513df --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/Rules/TerracoinHeaderVersionRule.cs @@ -0,0 +1,49 @@ +using Blockcore.Consensus; +using Blockcore.Consensus.Chain; +using Blockcore.Consensus.Rules; +using Blockcore.Features.Consensus.Rules.CommonRules; +using Blockcore.Utilities; +using Microsoft.Extensions.Logging; + +namespace Terracoin.Networks.Rules +{ + /// + /// Checks if network block's header has a valid block version. + /// + public class TerracoinHeaderVersionRule : HeaderVersionRule + { + /// + /// Thrown if block's version is outdated or otherwise invalid. + public override void Run(RuleContext context) + { + Guard.NotNull(context.ValidationContext.ChainedHeaderToValidate, nameof(context.ValidationContext.ChainedHeaderToValidate)); + + ChainedHeader chainedHeader = context.ValidationContext.ChainedHeaderToValidate; + + // A version of precisely 7 is what is currently generated by the legacy C++ nodes. + + // The stratisX block validation rules mandate if (!IsProtocolV3(nTime)) && (nVersion > 7), then reject block. + // Further, if (IsProtocolV2(nHeight) && nVersion < 7), then reject block. + // And lastly, if (!IsProtocolV2(nHeight) && nVersion > 6), then reject block. + + // Protocol version determination is based on either the block height or timestamp as shown: + // IsProtocolV2(nHeight) { return TestNet() || nHeight > 0; } + // IsProtocolV3(nTime) { return TestNet() || nTime > 1470467000; } + + // The mainnet genesis block has nTime = 1470713393, so V3 is applied immediately and this supersedes V2. + // The block versions have therefore been version 7 since genesis on Stratis mainnet. + + // Whereas BIP9 mandates that the top bits of version be 001. So a standard node should never generate + // block versions above 7 and below 0x18444000. + + // The acceptable common subset of the rules is therefore that the block version must be >= 7. + + if (chainedHeader.Header.Version < 7) + { + Logger.LogTrace("(-)[BAD_VERSION]"); + + ConsensusErrors.BadVersion.Throw(); + } + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/Setup/CoinSetup.cs b/src/Networks/Terracoin/Terracoin/Networks/Setup/CoinSetup.cs new file mode 100644 index 000000000..e41df2f97 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/Setup/CoinSetup.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Blockcore.Consensus.Checkpoints; +using NBitcoin; + +namespace Terracoin.Networks.Setup +{ + internal class CoinSetup + { + internal string FileNamePrefix; + internal string ConfigFileName; + internal string Magic; + internal int CoinType; + internal decimal PremineReward; + internal decimal PoWBlockReward; + internal decimal PoSBlockReward; + internal int LastPowBlock; + internal string GenesisText; + internal TimeSpan TargetSpacing; + internal uint ProofOfStakeTimestampMask; + internal int PoSVersion; + } + + internal class NetworkSetup + { + internal string Name; + internal string RootFolderName; + internal string CoinTicker; + internal int DefaultPort; + internal int DefaultRPCPort; + internal int DefaultAPIPort; + internal int PubKeyAddress; + internal int ScriptAddress; + internal int SecretAddress; + internal uint GenesisTime; + internal uint GenesisNonce; + internal uint GenesisBits; + internal int GenesisVersion; + internal Money GenesisReward; + internal string HashGenesisBlock; + internal string HashMerkleRoot; + internal string[] DNS; + internal string[] Nodes; + internal Dictionary Checkpoints; + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/Setup/ConversionTools.cs b/src/Networks/Terracoin/Terracoin/Networks/Setup/ConversionTools.cs new file mode 100644 index 000000000..ebe7448f8 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/Setup/ConversionTools.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Terracoin.Networks.Setup +{ + public class ConversionTools + { + public static uint ConvertToUInt32(string magicText, bool reverse = false) + { + byte[] number = magicText.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray(); + + if (reverse) + { + Array.Reverse(number); + } + + return BitConverter.ToUInt32(number); + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/TerracoinMain.cs b/src/Networks/Terracoin/Terracoin/Networks/TerracoinMain.cs new file mode 100644 index 000000000..fe329b081 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/TerracoinMain.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using Blockcore.Features.Consensus.Rules.CommonRules; +using Blockcore.Features.Consensus.Rules.ProvenHeaderRules; +using Blockcore.Features.Consensus.Rules.UtxosetRules; +using Blockcore.Features.MemoryPool.Rules; +using Terracoin.Networks.Policies; +using Terracoin.Networks.Rules; +using NBitcoin; +using NBitcoin.BouncyCastle.Math; +using NBitcoin.DataEncoders; +using System.Collections; +using System.Linq; +using System.Collections.Specialized; +using System.Net; +using Terracoin.Networks.Setup; +using Blockcore.Networks; +using Blockcore.Base.Deployments; +using Blockcore.Consensus.BlockInfo; +using Blockcore.Consensus; +using Blockcore.P2P; +using Blockcore.Consensus.TransactionInfo; +using Blockcore.Consensus.ScriptInfo; +using Terracoin.Networks.Deployments; + +namespace Terracoin.Networks +{ + public class TerracoinMain : Network + { + public TerracoinMain() + { + CoinSetup setup = TerracoinSetup.Instance.Setup; + NetworkSetup network = TerracoinSetup.Instance.Main; + + NetworkType = NetworkType.Mainnet; + DefaultConfigFilename = setup.ConfigFileName; // The default name used for the Terracoin configuration file. + + Name = network.Name; + CoinTicker = network.CoinTicker; + Magic = ConversionTools.ConvertToUInt32(setup.Magic); + RootFolderName = network.RootFolderName; + DefaultPort = network.DefaultPort; + DefaultRPCPort = network.DefaultRPCPort; + DefaultAPIPort = network.DefaultAPIPort; + + DefaultMaxOutboundConnections = 16; + DefaultMaxInboundConnections = 109; + MaxTipAge = 2 * 60 * 60; + MinTxFee = 10000; + MaxTxFee = Money.Coins(1).Satoshi; + FallbackFee = 0; + MinRelayTxFee = 10000; + MaxTimeOffsetSeconds = 25 * 60; + DefaultBanTimeSeconds = 16000; // 500 (MaxReorg) * 64 (TargetSpacing) / 2 = 4 hours, 26 minutes and 40 seconds + + var consensusFactory = new PosConsensusFactory(); + + // Create the genesis block. + GenesisTime = network.GenesisTime; + GenesisNonce = network.GenesisNonce; + GenesisBits = network.GenesisBits; + GenesisVersion = network.GenesisVersion; + GenesisReward = network.GenesisReward; + + Block genesisBlock = CreateGenesisBlock(consensusFactory, + GenesisTime, + GenesisNonce, + GenesisBits, + GenesisVersion, + GenesisReward, + setup.GenesisText); + + Genesis = genesisBlock; + + var consensusOptions = new PosConsensusOptions + { + MaxBlockBaseSize = 1_000_000, + MaxStandardVersion = 2, + MaxStandardTxWeight = 100_000, + MaxBlockSigopsCost = 20_000, + MaxStandardTxSigopsCost = 20_000 / 5, + WitnessScaleFactor = 4 + }; + + var buriedDeployments = new BuriedDeploymentsArray + { + [BuriedDeployments.BIP34] = 0, + [BuriedDeployments.BIP65] = 0, + [BuriedDeployments.BIP66] = 0 + }; + + var bip9Deployments = new TerracoinBIP9Deployments() + { + [TerracoinBIP9Deployments.ColdStaking] = new BIP9DeploymentsParameters("ColdStaking", 2, + new DateTime(2018, 12, 1, 0, 0, 0, DateTimeKind.Utc), + new DateTime(2019, 12, 1, 0, 0, 0, DateTimeKind.Utc), + BIP9DeploymentsParameters.DefaultMainnetThreshold) + }; + + Consensus = new Blockcore.Consensus.Consensus( + consensusFactory: consensusFactory, + consensusOptions: consensusOptions, + coinType: setup.CoinType, + hashGenesisBlock: genesisBlock.GetHash(), + subsidyHalvingInterval: 210000, + majorityEnforceBlockUpgrade: 750, + majorityRejectBlockOutdated: 950, + majorityWindow: 1000, + buriedDeployments: buriedDeployments, + bip9Deployments: bip9Deployments, + bip34Hash: null, + minerConfirmationWindow: 2016, // nPowTargetTimespan / nPowTargetSpacing + maxReorgLength: 500, + defaultAssumeValid: null, + maxMoney: long.MaxValue, + coinbaseMaturity: 50, + premineHeight: 2, + premineReward: Money.Coins(setup.PremineReward), + proofOfWorkReward: Money.Coins(setup.PoWBlockReward), + targetTimespan: TimeSpan.FromSeconds(14 * 24 * 60 * 60), // two weeks + targetSpacing: setup.TargetSpacing, + powAllowMinDifficultyBlocks: false, + posNoRetargeting: false, + powNoRetargeting: false, + powLimit: new Target(new uint256("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")), + minimumChainWork: null, + isProofOfStake: true, + lastPowBlock: setup.LastPowBlock, + proofOfStakeLimit: new BigInteger(uint256.Parse("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), + proofOfStakeLimitV2: new BigInteger(uint256.Parse("000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), + proofOfStakeReward: Money.Coins(setup.PoSBlockReward), + proofOfStakeTimestampMask: setup.ProofOfStakeTimestampMask + ); + + Consensus.PosEmptyCoinbase = TerracoinSetup.Instance.IsPoSv3(); + Consensus.PosUseTimeFieldInKernalHash = TerracoinSetup.Instance.IsPoSv3(); + + // TODO: Set your Base58Prefixes + Base58Prefixes = new byte[12][]; + Base58Prefixes[(int)Base58Type.PUBKEY_ADDRESS] = new byte[] { (byte)network.PubKeyAddress }; + Base58Prefixes[(int)Base58Type.SCRIPT_ADDRESS] = new byte[] { (byte)network.ScriptAddress }; + Base58Prefixes[(int)Base58Type.SECRET_KEY] = new byte[] { (byte)network.SecretAddress }; + + Base58Prefixes[(int)Base58Type.ENCRYPTED_SECRET_KEY_NO_EC] = new byte[] { 0x01, 0x42 }; + Base58Prefixes[(int)Base58Type.ENCRYPTED_SECRET_KEY_EC] = new byte[] { 0x01, 0x43 }; + Base58Prefixes[(int)Base58Type.EXT_PUBLIC_KEY] = new byte[] { (0x04), (0x88), (0xB2), (0x1E) }; + Base58Prefixes[(int)Base58Type.EXT_SECRET_KEY] = new byte[] { (0x04), (0x88), (0xAD), (0xE4) }; + Base58Prefixes[(int)Base58Type.PASSPHRASE_CODE] = new byte[] { 0x2C, 0xE9, 0xB3, 0xE1, 0xFF, 0x39, 0xE2 }; + Base58Prefixes[(int)Base58Type.CONFIRMATION_CODE] = new byte[] { 0x64, 0x3B, 0xF6, 0xA8, 0x9A }; + Base58Prefixes[(int)Base58Type.ASSET_ID] = new byte[] { 23 }; + + Bech32Encoders = new Bech32Encoder[2]; + var encoder = new Bech32Encoder(network.CoinTicker.ToLowerInvariant()); + Bech32Encoders[(int)Bech32Type.WITNESS_PUBKEY_ADDRESS] = encoder; + Bech32Encoders[(int)Bech32Type.WITNESS_SCRIPT_ADDRESS] = encoder; + + Checkpoints = network.Checkpoints; + DNSSeeds = network.DNS.Select(dns => new DNSSeedData(dns, dns)).ToList(); + SeedNodes = network.Nodes.Select(node => new NBitcoin.Protocol.NetworkAddress(IPAddress.Parse(node), network.DefaultPort)).ToList(); + + StandardScriptsRegistry = new TerracoinStandardScriptsRegistry(); + + // 64 below should be changed to TargetSpacingSeconds when we move that field. + Assert(DefaultBanTimeSeconds <= Consensus.MaxReorgLength * 64 / 2); + + Assert(Consensus.HashGenesisBlock == uint256.Parse(network.HashGenesisBlock)); + Assert(Genesis.Header.HashMerkleRoot == uint256.Parse(network.HashMerkleRoot)); + + RegisterRules(Consensus); + RegisterMempoolRules(Consensus); + } + + protected void RegisterRules(IConsensus consensus) + { + consensus.ConsensusRules + .Register() + .Register() + .Register() + .Register() + .Register() + .Register() + .Register(); + + consensus.ConsensusRules + .Register() + .Register() + .Register(); + + consensus.ConsensusRules + .Register() + .Register() + + // rules that are inside the method ContextualCheckBlock + .Register() + .Register() + .Register() + .Register() + + // rules that are inside the method CheckBlock + .Register() + .Register() + .Register() + .Register() + .Register(); + + consensus.ConsensusRules + .Register() + + .Register() + + // rules that require the store to be loaded (coinview) + .Register() + .Register() + .Register() // implements BIP68, MaxSigOps and BlockReward calculation + // Place the PosColdStakingRule after the PosCoinviewRule to ensure that all input scripts have been evaluated + // and that the "IsColdCoinStake" flag would have been set by the OP_CHECKCOLDSTAKEVERIFY opcode if applicable. + .Register() + .Register() + .Register(); + } + + protected void RegisterMempoolRules(IConsensus consensus) + { + consensus.MempoolRules = new List() + { + typeof(CheckConflictsMempoolRule), + typeof(CheckCoinViewMempoolRule), + typeof(CreateMempoolEntryMempoolRule), + typeof(CheckSigOpsMempoolRule), + typeof(CheckFeeMempoolRule), + typeof(CheckRateLimitMempoolRule), + typeof(CheckAncestorsMempoolRule), + typeof(CheckReplacementMempoolRule), + typeof(CheckAllInputsMempoolRule), + typeof(CheckTxOutDustRule) + }; + } + + protected static Block CreateGenesisBlock(ConsensusFactory consensusFactory, uint nTime, uint nNonce, uint nBits, int nVersion, Money genesisReward, string genesisText) + { + Transaction txNew = consensusFactory.CreateTransaction(); + txNew.Version = 1; + + if (txNew is IPosTransactionWithTime posTx) + { + posTx.Time = nTime; + } + + txNew.AddInput(new TxIn() + { + ScriptSig = new Script(Op.GetPushOp(0), new Op() + { + Code = (OpcodeType)0x1, + PushData = new[] { (byte)42 } + }, Op.GetPushOp(Encoders.ASCII.DecodeData(genesisText))) + }); + + txNew.AddOutput(new TxOut() + { + Value = genesisReward, + }); + + Block genesis = consensusFactory.CreateBlock(); + genesis.Header.BlockTime = Utils.UnixTimeToDateTime(nTime); + genesis.Header.Bits = nBits; + genesis.Header.Nonce = nNonce; + genesis.Header.Version = nVersion; + genesis.Transactions.Add(txNew); + genesis.Header.HashPrevBlock = uint256.Zero; + genesis.UpdateMerkleRoot(); + + return genesis; + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/TerracoinRegTest.cs b/src/Networks/Terracoin/Terracoin/Networks/TerracoinRegTest.cs new file mode 100644 index 000000000..bd0251662 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/TerracoinRegTest.cs @@ -0,0 +1,133 @@ +using System; +using System.Linq; +using System.Net; +using Blockcore.Base.Deployments; +using Blockcore.Consensus; +using Blockcore.Consensus.BlockInfo; +using Blockcore.Networks; +using Blockcore.P2P; +using Terracoin.Networks.Policies; +using Terracoin.Networks.Setup; +using NBitcoin; +using NBitcoin.BouncyCastle.Math; +using NBitcoin.DataEncoders; + +namespace Terracoin.Networks +{ + public class TerracoinRegTest : TerracoinMain + { + public TerracoinRegTest() + { + CoinSetup setup = TerracoinSetup.Instance.Setup; + NetworkSetup network = TerracoinSetup.Instance.RegTest; + + NetworkType = NetworkType.Regtest; + + Name = network.Name; + CoinTicker = network.CoinTicker; + Magic = ConversionTools.ConvertToUInt32(setup.Magic, true); + RootFolderName = network.RootFolderName; + DefaultPort = network.DefaultPort; + DefaultRPCPort = network.DefaultRPCPort; + DefaultAPIPort = network.DefaultAPIPort; + + var consensusFactory = new PosConsensusFactory(); + + // Create the genesis block. + GenesisTime = network.GenesisTime; + GenesisNonce = network.GenesisNonce; + GenesisBits = network.GenesisBits; + GenesisVersion = network.GenesisVersion; + GenesisReward = network.GenesisReward; + + Block genesisBlock = CreateGenesisBlock(consensusFactory, + GenesisTime, + GenesisNonce, + GenesisBits, + GenesisVersion, + GenesisReward, + setup.GenesisText); + + Genesis = genesisBlock; + + var consensusOptions = new PosConsensusOptions + { + MaxBlockBaseSize = 1_000_000, + MaxStandardVersion = 2, + MaxStandardTxWeight = 100_000, + MaxBlockSigopsCost = 20_000, + MaxStandardTxSigopsCost = 20_000 / 5, + WitnessScaleFactor = 4 + }; + + var buriedDeployments = new BuriedDeploymentsArray + { + [BuriedDeployments.BIP34] = 0, + [BuriedDeployments.BIP65] = 0, + [BuriedDeployments.BIP66] = 0 + }; + + Consensus = new Blockcore.Consensus.Consensus( + consensusFactory: consensusFactory, + consensusOptions: consensusOptions, + coinType: setup.CoinType, + hashGenesisBlock: genesisBlock.GetHash(), + subsidyHalvingInterval: 210000, + majorityEnforceBlockUpgrade: 750, + majorityRejectBlockOutdated: 950, + majorityWindow: 1000, + buriedDeployments: buriedDeployments, + bip9Deployments: new NoBIP9Deployments(), + bip34Hash: null, + minerConfirmationWindow: 2016, // nPowTargetTimespan / nPowTargetSpacing + maxReorgLength: 500, + defaultAssumeValid: null, + maxMoney: long.MaxValue, + coinbaseMaturity: 10, + premineHeight: 2, + premineReward: Money.Coins(setup.PremineReward), + proofOfWorkReward: Money.Coins(setup.PoWBlockReward), + targetTimespan: TimeSpan.FromSeconds(14 * 24 * 60 * 60), // two weeks + targetSpacing: setup.TargetSpacing, + powAllowMinDifficultyBlocks: true, + posNoRetargeting: true, + powNoRetargeting: true, + powLimit: new Target(new uint256("0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")), + minimumChainWork: null, + isProofOfStake: true, + lastPowBlock: setup.LastPowBlock, + proofOfStakeLimit: new BigInteger(uint256.Parse("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), + proofOfStakeLimitV2: new BigInteger(uint256.Parse("000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), + proofOfStakeReward: Money.Coins(setup.PoSBlockReward), + proofOfStakeTimestampMask: setup.ProofOfStakeTimestampMask + ); + + Base58Prefixes[(int)Base58Type.PUBKEY_ADDRESS] = new byte[] { (byte)network.PubKeyAddress }; + Base58Prefixes[(int)Base58Type.SCRIPT_ADDRESS] = new byte[] { (byte)network.ScriptAddress }; + Base58Prefixes[(int)Base58Type.SECRET_KEY] = new byte[] { (239) }; + Base58Prefixes[(int)Base58Type.EXT_PUBLIC_KEY] = new byte[] { (0x04), (0x35), (0x87), (0xCF) }; + Base58Prefixes[(int)Base58Type.EXT_SECRET_KEY] = new byte[] { (0x04), (0x35), (0x83), (0x94) }; + Base58Prefixes[(int)Base58Type.ASSET_ID] = new byte[] { 115 }; + + Bech32Encoders = new Bech32Encoder[2]; + var encoder = new Bech32Encoder(network.CoinTicker.ToLowerInvariant()); + Bech32Encoders[(int)Bech32Type.WITNESS_PUBKEY_ADDRESS] = encoder; + Bech32Encoders[(int)Bech32Type.WITNESS_SCRIPT_ADDRESS] = encoder; + + Checkpoints = network.Checkpoints; + DNSSeeds = network.DNS.Select(dns => new DNSSeedData(dns, dns)).ToList(); + SeedNodes = network.Nodes.Select(node => new NBitcoin.Protocol.NetworkAddress(IPAddress.Parse(node), network.DefaultPort)).ToList(); + + StandardScriptsRegistry = new TerracoinStandardScriptsRegistry(); + + // 64 below should be changed to TargetSpacingSeconds when we move that field. + Assert(DefaultBanTimeSeconds <= Consensus.MaxReorgLength * 64 / 2); + + Assert(Consensus.HashGenesisBlock == uint256.Parse(network.HashGenesisBlock)); + Assert(Genesis.Header.HashMerkleRoot == uint256.Parse(network.HashMerkleRoot)); + + RegisterRules(Consensus); + RegisterMempoolRules(Consensus); + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Networks/TerracoinTest.cs b/src/Networks/Terracoin/Terracoin/Networks/TerracoinTest.cs new file mode 100644 index 000000000..e91182c27 --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Networks/TerracoinTest.cs @@ -0,0 +1,137 @@ +using System; +using System.Linq; +using System.Net; +using Blockcore.Base.Deployments; +using Blockcore.Consensus; +using Blockcore.Consensus.BlockInfo; +using Blockcore.Networks; +using Blockcore.P2P; +using Terracoin.Networks.Policies; +using Terracoin.Networks.Setup; +using NBitcoin; +using NBitcoin.BouncyCastle.Math; +using NBitcoin.DataEncoders; + +namespace Terracoin.Networks +{ + public class TerracoinTest : TerracoinMain + { + public TerracoinTest() + { + CoinSetup setup = TerracoinSetup.Instance.Setup; + NetworkSetup network = TerracoinSetup.Instance.Test; + + NetworkType = NetworkType.Testnet; + + Name = network.Name; + CoinTicker = network.CoinTicker; + Magic = ConversionTools.ConvertToUInt32(setup.Magic, true); + RootFolderName = network.RootFolderName; + DefaultPort = network.DefaultPort; + DefaultRPCPort = network.DefaultRPCPort; + DefaultAPIPort = network.DefaultAPIPort; + + var consensusFactory = new PosConsensusFactory(); + + // Create the genesis block. + GenesisTime = network.GenesisTime; + GenesisNonce = network.GenesisNonce; + GenesisBits = network.GenesisBits; + GenesisVersion = network.GenesisVersion; + GenesisReward = network.GenesisReward; + + Block genesisBlock = CreateGenesisBlock(consensusFactory, + GenesisTime, + GenesisNonce, + GenesisBits, + GenesisVersion, + GenesisReward, + setup.GenesisText); + + Genesis = genesisBlock; + + var consensusOptions = new PosConsensusOptions + { + MaxBlockBaseSize = 1_000_000, + MaxStandardVersion = 2, + MaxStandardTxWeight = 100_000, + MaxBlockSigopsCost = 20_000, + MaxStandardTxSigopsCost = 20_000 / 5, + WitnessScaleFactor = 4 + }; + + var buriedDeployments = new BuriedDeploymentsArray + { + [BuriedDeployments.BIP34] = 0, + [BuriedDeployments.BIP65] = 0, + [BuriedDeployments.BIP66] = 0 + }; + + Consensus = new Blockcore.Consensus.Consensus( + consensusFactory: consensusFactory, + consensusOptions: consensusOptions, + coinType: setup.CoinType, + hashGenesisBlock: genesisBlock.GetHash(), + subsidyHalvingInterval: 210000, + majorityEnforceBlockUpgrade: 750, + majorityRejectBlockOutdated: 950, + majorityWindow: 1000, + buriedDeployments: buriedDeployments, + bip9Deployments: new NoBIP9Deployments(), + bip34Hash: null, + minerConfirmationWindow: 2016, // nPowTargetTimespan / nPowTargetSpacing + maxReorgLength: 500, + defaultAssumeValid: null, + maxMoney: long.MaxValue, + coinbaseMaturity: 10, + premineHeight: 2, + premineReward: Money.Coins(setup.PremineReward), + proofOfWorkReward: Money.Coins(setup.PoWBlockReward), + targetTimespan: TimeSpan.FromSeconds(14 * 24 * 60 * 60), // two weeks + targetSpacing: setup.TargetSpacing, + powAllowMinDifficultyBlocks: false, + posNoRetargeting: false, + powNoRetargeting: false, + powLimit: new Target(new uint256("000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")), + minimumChainWork: null, + isProofOfStake: true, + lastPowBlock: setup.LastPowBlock, + proofOfStakeLimit: new BigInteger(uint256.Parse("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), + proofOfStakeLimitV2: new BigInteger(uint256.Parse("000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), + proofOfStakeReward: Money.Coins(setup.PoSBlockReward), + proofOfStakeTimestampMask: setup.ProofOfStakeTimestampMask + ); + + Base58Prefixes[(int)Base58Type.PUBKEY_ADDRESS] = new byte[] { (byte)network.PubKeyAddress }; + Base58Prefixes[(int)Base58Type.SCRIPT_ADDRESS] = new byte[] { (byte)network.ScriptAddress }; + Base58Prefixes[(int)Base58Type.SECRET_KEY] = new byte[] { (239) }; + Base58Prefixes[(int)Base58Type.ENCRYPTED_SECRET_KEY_NO_EC] = new byte[] { 0x01, 0x42 }; + Base58Prefixes[(int)Base58Type.ENCRYPTED_SECRET_KEY_EC] = new byte[] { 0x01, 0x43 }; + Base58Prefixes[(int)Base58Type.EXT_PUBLIC_KEY] = new byte[] { (0x04), (0x88), (0xB2), (0x1E) }; + Base58Prefixes[(int)Base58Type.EXT_SECRET_KEY] = new byte[] { (0x04), (0x88), (0xAD), (0xE4) }; + Base58Prefixes[(int)Base58Type.PASSPHRASE_CODE] = new byte[] { 0x2C, 0xE9, 0xB3, 0xE1, 0xFF, 0x39, 0xE2 }; + Base58Prefixes[(int)Base58Type.CONFIRMATION_CODE] = new byte[] { 0x64, 0x3B, 0xF6, 0xA8, 0x9A }; + Base58Prefixes[(int)Base58Type.ASSET_ID] = new byte[] { 115 }; + + Bech32Encoders = new Bech32Encoder[2]; + var encoder = new Bech32Encoder(network.CoinTicker.ToLowerInvariant()); + Bech32Encoders[(int)Bech32Type.WITNESS_PUBKEY_ADDRESS] = encoder; + Bech32Encoders[(int)Bech32Type.WITNESS_SCRIPT_ADDRESS] = encoder; + + Checkpoints = network.Checkpoints; + DNSSeeds = network.DNS.Select(dns => new DNSSeedData(dns, dns)).ToList(); + SeedNodes = network.Nodes.Select(node => new NBitcoin.Protocol.NetworkAddress(IPAddress.Parse(node), network.DefaultPort)).ToList(); + + StandardScriptsRegistry = new TerracoinStandardScriptsRegistry(); + + // 64 below should be changed to TargetSpacingSeconds when we move that field. + Assert(DefaultBanTimeSeconds <= Consensus.MaxReorgLength * 64 / 2); + + Assert(Consensus.HashGenesisBlock == uint256.Parse(network.HashGenesisBlock)); + Assert(Genesis.Header.HashMerkleRoot == uint256.Parse(network.HashMerkleRoot)); + + RegisterRules(Consensus); + RegisterMempoolRules(Consensus); + } + } +} diff --git a/src/Networks/Terracoin/Terracoin/Terracoin.csproj b/src/Networks/Terracoin/Terracoin/Terracoin.csproj new file mode 100644 index 000000000..f87e0243b --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/Terracoin.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + 3.1.0 + Terracoin.Networks + Blockcore + + + + latest + + + + + + + + + + diff --git a/src/Networks/Terracoin/Terracoin/TerracoinSetup.cs b/src/Networks/Terracoin/Terracoin/TerracoinSetup.cs new file mode 100644 index 000000000..eb23053dc --- /dev/null +++ b/src/Networks/Terracoin/Terracoin/TerracoinSetup.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using Blockcore.Consensus.Checkpoints; +using Terracoin.Networks; +using Terracoin.Networks.Setup; +using NBitcoin; + +namespace Terracoin +{ + internal class TerracoinSetup + { + internal static TerracoinSetup Instance = new TerracoinSetup(); + + internal CoinSetup Setup = new CoinSetup + { + FileNamePrefix = "terracoin", + ConfigFileName = "terracoin.conf", + Magic = "1455340098", + CoinType = 83, // SLIP-0044: https://github.com/satoshilabs/slips/blob/master/slip-0044.md, + PremineReward = 0, + PoWBlockReward = 20, + PoSBlockReward = 0, + LastPowBlock = 0, + GenesisText = "June 4th 1978 - March 6th 2009 ; Rest In Peace, Stephanie.", // The New York Times, 2020-04-16 + TargetSpacing = TimeSpan.FromSeconds(64), + ProofOfStakeTimestampMask = 0x0000000F, // 0x0000003F // 64 sec + PoSVersion = 3 + }; + + internal NetworkSetup Main = new NetworkSetup + { + Name = "TerracoinMain", + RootFolderName = "terracoin", + CoinTicker = "TRC", + DefaultPort = 13333, + DefaultRPCPort = 13332, + DefaultAPIPort = 13332, + PubKeyAddress = 111, // B https://en.bitcoin.it/wiki/List_of_address_prefixes + ScriptAddress = 196, // b + SecretAddress = 239, + GenesisTime = --genesis-nonce-main, + GenesisNonce = 631024, + GenesisBits = 0x, + GenesisVersion = 1, + GenesisReward = Money.Zero, + HashGenesisBlock = "", + HashMerkleRoot = "", + DNS = new[] { "seed.terracoin.io", "dnsseed.southofheaven.ca", "trc.seed.blockcore.net" }, + Nodes = new[] { "104.238.156.46", "107.170.238.241" }, + Checkpoints = new Dictionary + { + // TODO: Add checkpoints as the network progresses. + } + }; + + internal NetworkSetup RegTest = new NetworkSetup + { + Name = "TerracoinRegTest", + RootFolderName = "terracoinregtest", + CoinTicker = "TTRC", + DefaultPort = 18444, + DefaultRPCPort = 18332, + DefaultAPIPort = 18332, + PubKeyAddress = 111, + ScriptAddress = 196, + SecretAddress = 239, + GenesisTime = --genesis-nonce-regtest, + GenesisNonce = 41450, + GenesisBits = 0x, + GenesisVersion = 1, + GenesisReward = Money.Zero, + HashGenesisBlock = "", + HashMerkleRoot = "", + DNS = new[] { "seedregtest1.trc.blockcore.net", "seedregtest2.trc.blockcore.net", "seedregtest.trc.blockcore.net" }, + Nodes = new[] { "104.238.156.46", "107.170.238.241" }, + Checkpoints = new Dictionary + { + // TODO: Add checkpoints as the network progresses. + } + }; + + internal NetworkSetup Test = new NetworkSetup + { + Name = "TerracoinTest", + RootFolderName = "terracointest", + CoinTicker = "TTRC", + DefaultPort = 18321, + DefaultRPCPort = 18322, + DefaultAPIPort = 18322, + PubKeyAddress = 111, + ScriptAddress = 196, + SecretAddress = 239, + GenesisTime = --genesis-nonce-test, + GenesisNonce = 4834, + GenesisBits = 0x, + GenesisVersion = 1, + GenesisReward = Money.Zero, + HashGenesisBlock = "", + HashMerkleRoot = "", + DNS = new[] { "seedtest1.trc.blockcore.net", "seedtest2.trc.blockcore.net", "seedtest.trc.blockcore.net" }, + Nodes = new[] { "104.238.156.46", "107.170.238.241" }, + Checkpoints = new Dictionary + { + // TODO: Add checkpoints as the network progresses. + } + }; + + public bool IsPoSv3() + { + return Setup.PoSVersion == 3; + } + + public bool IsPoSv4() + { + return Setup.PoSVersion == 4; + } + } +}