public PeerChainStateQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <ListGraphType <StringGraphType> > >( name: "state", resolve: context => { var service = standaloneContext.NineChroniclesNodeService; if (service is null) { Log.Error($"{nameof(NineChroniclesNodeService)} is null."); return(null); } var swarm = service.Swarm; var chain = swarm.BlockChain; var chainStates = new List <string> { $"{swarm.AsPeer.Address}, {chain.Tip.Index}, {chain.Tip.TotalDifficulty}" }; var peerChainState = swarm.GetPeerChainStateAsync( TimeSpan.FromSeconds(5), default) .Result .Select( state => $"{state.Peer.Address}, {state.TipIndex}, {state.TotalDifficulty}"); chainStates.AddRange(peerChainState); return(chainStates); } ); }
public GraphQLTestBase(ITestOutputHelper output) { Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); _output = output; var store = new DefaultStore(null); var genesisBlock = BlockChain <PolymorphicAction <ActionBase> > .MakeGenesisBlock(); var blockPolicy = new BlockPolicy <PolymorphicAction <ActionBase> >(blockAction: new RewardGold()); var blockChain = new BlockChain <PolymorphicAction <ActionBase> >( blockPolicy, store, store, genesisBlock, renderers: new IRenderer <PolymorphicAction <ActionBase> >[] { new BlockRenderer(), new ActionRenderer() } ); var tempKeyStorePath = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); var keyStore = new Web3KeyStore(tempKeyStorePath); StandaloneContextFx = new StandaloneContext { BlockChain = blockChain, KeyStore = keyStore, }; Schema = new StandaloneSchema(new TestServiceProvider(StandaloneContextFx)); Schema.Subscription.As <StandaloneSubscription>().RegisterTipChangedSubscription(); DocumentExecutor = new DocumentExecuter(); }
public GraphQLControllerTest() { var store = new DefaultStore(null); var stateStore = new TrieStateStore( new DefaultKeyValueStore(null), new DefaultKeyValueStore(null)); var genesisBlock = BlockChain <PolymorphicAction <ActionBase> > .MakeGenesisBlock(); var blockchain = new BlockChain <PolymorphicAction <ActionBase> >( new BlockPolicy <PolymorphicAction <ActionBase> >(), new VolatileStagePolicy <PolymorphicAction <ActionBase> >(), store, stateStore, genesisBlock); _standaloneContext = new StandaloneContext { BlockChain = blockchain, Store = store, }; _configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); _httpContextAccessor = new HttpContextAccessor(); _httpContextAccessor.HttpContext = new DefaultHttpContext(); _controller = new GraphQLController(_standaloneContext, _httpContextAccessor, _configuration); }
public StandaloneMutation( StandaloneContext standaloneContext, NineChroniclesNodeService nodeService, IConfiguration configuration ) { if (configuration[GraphQLService.SecretTokenKey] is { })
public TestServiceProvider(StandaloneContext standaloneContext) { Query = new StandaloneQuery(standaloneContext); Mutation = new StandaloneMutation(standaloneContext); Subscription = new StandaloneSubscription(standaloneContext); StandaloneContext = standaloneContext; }
public GraphQLTestBase(ITestOutputHelper output) { Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); _output = output; var goldCurrency = new Currency("NCG", 2, minter: null); var fixturePath = Path.Combine("..", "..", "..", "..", "Lib9c", ".Lib9c.Tests", "Data", "TableCSV"); var sheets = TableSheetsImporter.ImportSheets(fixturePath); var blockAction = new RewardGold(); var genesisBlock = BlockChain <NCAction> .MakeGenesisBlock( new NCAction[] { new InitializeStates( rankingState: new RankingState(), shopState: new ShopState(), gameConfigState: new GameConfigState(sheets[nameof(GameConfigSheet)]), redeemCodeState: new RedeemCodeState(Bencodex.Types.Dictionary.Empty .Add("address", RedeemCodeState.Address.Serialize()) .Add("map", Bencodex.Types.Dictionary.Empty) ), adminAddressState: new AdminState(AdminAddress, 10000), activatedAccountsState: new ActivatedAccountsState(), goldCurrencyState: new GoldCurrencyState(goldCurrency), goldDistributions: new GoldDistribution[] { }, tableSheets: sheets, pendingActivationStates: new PendingActivationState[] { } ), }, blockAction : blockAction); var ncService = ServiceBuilder.CreateNineChroniclesNodeService(genesisBlock, new PrivateKey()); var tempKeyStorePath = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); var keyStore = new Web3KeyStore(tempKeyStorePath); StandaloneContextFx = new StandaloneContext { KeyStore = keyStore, }; ncService.ConfigureContext(StandaloneContextFx); var configurationBuilder = new ConfigurationBuilder(); var configuration = configurationBuilder.Build(); var services = new ServiceCollection(); services.AddSingleton(StandaloneContextFx); services.AddSingleton <IConfiguration>(configuration); services.AddGraphTypes(); services.AddLibplanetExplorer <NCAction>(); services.AddSingleton <StateQuery>(); services.AddSingleton(ncService); ServiceProvider serviceProvider = services.BuildServiceProvider(); Schema = new StandaloneSchema(serviceProvider); DocumentExecutor = new DocumentExecuter(); }
public ActivationStatusQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <BooleanGraphType> >( name: "activated", resolve: context => { var service = standaloneContext.NineChroniclesNodeService; if (service is null) { return(false); } try { if (!(service.MinerPrivateKey is { } privateKey)) { throw new InvalidOperationException($"{nameof(service.MinerPrivateKey)} is null."); } if (!(service.Swarm?.BlockChain is { } blockChain)) { throw new InvalidOperationException($"{nameof(service.Swarm.BlockChain)} is null."); } Address userAddress = privateKey.ToAddress(); Address activatedAddress = userAddress.Derive(ActivationKey.DeriveKey); if (blockChain.GetState(activatedAddress) is Bencodex.Types.Boolean) { return(true); } // Preserve previous check code due to migration period. // TODO: Remove this code after v100061+ IValue state = blockChain.GetState(ActivatedAccountsState.Address); if (state is Bencodex.Types.Dictionary asDict) { var activatedAccountsState = new ActivatedAccountsState(asDict); var activatedAccounts = activatedAccountsState.Accounts; return(activatedAccounts.Count == 0 || activatedAccounts.Contains(userAddress)); } return(true); } catch (Exception e) { var msg = "Unexpected exception occurred during ActivationStatusQuery: {e}"; context.Errors.Add(new ExecutionError(msg, e)); Log.Error(msg, e); return(false); } } ); }
public StandaloneMutation(StandaloneContext standaloneContext) { StandaloneContext = standaloneContext; Field <KeyStoreMutation>( name: "keyStore", resolve: context => standaloneContext.KeyStore); Field <ActivationStatusMutation>( name: "activationStatus", resolve: context => standaloneContext.NineChroniclesNodeService); }
public ActivationStatusQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <BooleanGraphType> >( name: "activated", resolve: context => { var service = standaloneContext.NineChroniclesNodeService; if (service is null) { return(false); } try { PrivateKey privateKey = service.PrivateKey; Address address = privateKey.ToAddress(); BlockChain <NineChroniclesActionType> blockChain = service.Swarm.BlockChain; IValue state = blockChain.GetState(ActivatedAccountsState.Address); if (state is Bencodex.Types.Dictionary asDict) { var activatedAccountsState = new ActivatedAccountsState(asDict); var activatedAccounts = activatedAccountsState.Accounts; return(activatedAccounts.Count == 0 || activatedAccounts.Contains(address)); } return(true); } catch (Exception e) { var msg = "Unexpected exception occurred during ActivationStatusQuery: {e}"; context.Errors.Add(new ExecutionError(msg, e)); Log.Error(msg, e); return(false); } } ); }
public PeerChainStateQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <ListGraphType <StringGraphType> > >( name: "state", description: "Summary of other peers connected to this node. It consists of address, chain height, and total difficulty.", resolve: context => { var service = standaloneContext.NineChroniclesNodeService; if (service is null) { Log.Error($"{nameof(NineChroniclesNodeService)} is null."); return(null); } var swarm = service.Swarm; if (!(swarm?.BlockChain is { } chain)) { throw new InvalidOperationException($"{nameof(swarm.BlockChain)} is null."); } var chainStates = new List <string> { $"{swarm.AsPeer.Address}, {chain.Tip.Index}, {chain.Tip.TotalDifficulty}" }; var peerChainState = swarm.GetPeerChainStateAsync( TimeSpan.FromSeconds(5), default) .Result .Select( state => $"{state.Peer.Address}, {state.TipIndex}, {state.TotalDifficulty}"); chainStates.AddRange(peerChainState); return(chainStates); } ); }
public GraphQLController(StandaloneContext standaloneContext, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) { _httpContextAccessor = httpContextAccessor; _configuration = configuration; StandaloneContext = standaloneContext; }
public StandaloneQuery(StandaloneContext standaloneContext) { Field <ByteStringType>( name: "state", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "address", Description = "The address of state to fetch from the chain." }, new QueryArgument <ByteStringType> { Name = "hash", Description = "The hash of the block used to fetch state from chain." } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } var address = context.GetArgument <Address>("address"); var blockHashByteArray = context.GetArgument <byte[]>("hash"); var blockHash = blockHashByteArray is null ? blockChain.Tip.Hash : new HashDigest <SHA256>(blockHashByteArray); var state = blockChain.GetState(address, blockHash); return(new Codec().Encode(state)); } ); Field <KeyStoreType>( name: "keyStore", resolve: context => standaloneContext.KeyStore ); Field <NonNullGraphType <NodeStatusType> >( name: "nodeStatus", resolve: context => new NodeStatusType { BootstrapEnded = standaloneContext.BootstrapEnded, PreloadEnded = standaloneContext.PreloadEnded, } ); Field <NonNullGraphType <ValidationQuery> >( name: "validation", description: "The validation method provider for Libplanet types.", resolve: context => new ValidationQuery(standaloneContext)); Field <NonNullGraphType <ActivationStatusQuery> >( name: "activationStatus", description: "Check if the provided address is activated.", resolve: context => new ActivationStatusQuery(standaloneContext)); Field <NonNullGraphType <PeerChainStateQuery> >( name: "peerChainState", description: "Get the peer's block chain state", resolve: context => new PeerChainStateQuery(standaloneContext)); }
public ValidationQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <BooleanGraphType> >( name: "metadata", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "raw", Description = "The raw value of json metadata." }), resolve: context => { var raw = context.GetArgument <string>("raw"); try { var remoteIndex = JsonDocument.Parse(raw).RootElement.GetProperty("Index").GetInt32(); Log.Debug("Remote: {index1}, Local: {index2}", remoteIndex, standaloneContext.BlockChain.Tip?.Index ?? -1); var ret = remoteIndex > (standaloneContext.BlockChain.Tip?.Index ?? -1); return(ret); } catch (JsonException je) { Log.Warning(je, "Given metadata is invalid. (raw: {raw})", raw); return(false); } catch (Exception e) { Log.Warning(e, "Unexpected exception occurred. (raw: {raw})", raw); return(false); } } ); Field <NonNullGraphType <BooleanGraphType> >( name: "privateKey", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <ByteStringType> > { Name = "hex", Description = "The raw value of private-key, presented as hexadecimal." }), resolve: context => { try { var rawPrivateKey = context.GetArgument <byte[]>("hex"); var _ = new PrivateKey(rawPrivateKey); return(true); } catch (ArgumentException) { return(false); } } ); Field <NonNullGraphType <BooleanGraphType> >( name: "publicKey", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <ByteStringType> > { Name = "hex", Description = "The raw value of public-key, presented as hexadecimal." }), resolve: context => { try { var rawPublicKey = context.GetArgument <byte[]>("hex"); var _ = new PublicKey(rawPublicKey); return(true); } catch (ArgumentException) { return(false); } catch (FormatException) { return(false); } } ); }
public StandaloneMutation(StandaloneContext standaloneContext) { Field <KeyStoreMutation>( name: "keyStore", resolve: context => standaloneContext.KeyStore); Field <ActivationStatusMutation>( name: "activationStatus", resolve: context => standaloneContext.NineChroniclesNodeService); Field <ActionMutation>( name: "action", resolve: context => standaloneContext.NineChroniclesNodeService); Field <NonNullGraphType <BooleanGraphType> >( name: "stageTx", description: "Add a new transaction to staging", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "payload", Description = "Hex-encoded bytes for new transaction." } ), resolve: context => { try { byte[] bytes = ByteUtil.ParseHex(context.GetArgument <string>("payload")); Transaction <NCAction> tx = Transaction <NCAction> .Deserialize(bytes); NineChroniclesNodeService service = standaloneContext.NineChroniclesNodeService; BlockChain <NCAction> blockChain = service.Swarm.BlockChain; if (blockChain.Policy.DoesTransactionFollowsPolicy(tx, blockChain)) { blockChain.StageTransaction(tx); return(true); } else { context.Errors.Add(new ExecutionError("The given transaction is invalid.")); return(false); } } catch (Exception e) { context.Errors.Add(new ExecutionError("An unexpected exception occurred.", e)); return(false); } } ); Field <TxIdType>( name: "transferGold", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "recipient", }, new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "amount" } ), resolve: context => { NineChroniclesNodeService service = standaloneContext.NineChroniclesNodeService; PrivateKey privateKey = service.PrivateKey; if (privateKey is null) { // FIXME We should cover this case on unittest. var msg = "No private key was loaded."; context.Errors.Add(new ExecutionError(msg)); Log.Error(msg); return(null); } BlockChain <NCAction> blockChain = service.BlockChain; var currency = new GoldCurrencyState( (Dictionary)blockChain.GetState(GoldCurrencyState.Address) ).Currency; FungibleAssetValue amount = FungibleAssetValue.Parse(currency, context.GetArgument <string>("amount")); Address recipient = context.GetArgument <Address>("recipient"); Transaction <NCAction> tx = blockChain.MakeTransaction( privateKey, new NCAction[] { new TransferAsset( privateKey.ToAddress(), recipient, amount ), } ); return(tx.Id); } ); }
public StandaloneQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <StateQuery <NCAction> > >(name: "stateQuery", resolve: _ => standaloneContext.BlockChain); Field <ByteStringType>( name: "state", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "address", Description = "The address of state to fetch from the chain." }, new QueryArgument <ByteStringType> { Name = "hash", Description = "The hash of the block used to fetch state from chain." } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } var address = context.GetArgument <Address>("address"); var blockHashByteArray = context.GetArgument <byte[]>("hash"); var blockHash = blockHashByteArray is null ? blockChain.Tip.Hash : new HashDigest <SHA256>(blockHashByteArray); var state = blockChain.GetState(address, blockHash); return(new Codec().Encode(state)); } ); Field <KeyStoreType>( name: "keyStore", resolve: context => standaloneContext.KeyStore ); Field <NonNullGraphType <NodeStatusType> >( name: "nodeStatus", resolve: context => new NodeStatusType { BootstrapEnded = standaloneContext.BootstrapEnded, PreloadEnded = standaloneContext.PreloadEnded, IsMining = standaloneContext.IsMining, BlockChain = standaloneContext.BlockChain, Store = standaloneContext.Store, } ); Field <NonNullGraphType <ValidationQuery> >( name: "validation", description: "The validation method provider for Libplanet types.", resolve: context => new ValidationQuery(standaloneContext)); Field <NonNullGraphType <ActivationStatusQuery> >( name: "activationStatus", description: "Check if the provided address is activated.", resolve: context => new ActivationStatusQuery(standaloneContext)); Field <NonNullGraphType <PeerChainStateQuery> >( name: "peerChainState", description: "Get the peer's block chain state", resolve: context => new PeerChainStateQuery(standaloneContext)); Field <NonNullGraphType <StringGraphType> >( name: "goldBalance", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "address", Description = "Target address to query" }, new QueryArgument <ByteStringType> { Name = "hash", Description = "Offset block hash for query." } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } Address address = context.GetArgument <Address>("address"); byte[] blockHashByteArray = context.GetArgument <byte[]>("hash"); var blockHash = blockHashByteArray is null ? blockChain.Tip.Hash : new HashDigest <SHA256>(blockHashByteArray); Currency currency = new GoldCurrencyState( (Dictionary)blockChain.GetState(GoldCurrencyState.Address) ).Currency; return(blockChain.GetBalance( address, currency, blockHash ).GetQuantityString()); } ); Field <NonNullGraphType <LongGraphType> >( name: "nextTxNonce", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "address", Description = "Target address to query" } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } Address address = context.GetArgument <Address>("address"); return(blockChain.GetNextTxNonce(address)); } ); }
public async Task Run( bool noMiner = false, [Option("app-protocol-version", new[] { 'V' }, Description = "App protocol version token")] string appProtocolVersionToken = null, [Option('G')] string genesisBlockPath = null, [Option('H')] string host = null, [Option('P')] ushort?port = null, [Option('D')] int minimumDifficulty = 5000000, [Option("private-key")] string privateKeyString = null, string storeType = null, string storePath = null, [Option("ice-server", new [] { 'I', })] string[] iceServerStrings = null, [Option("peer")] string[] peerStrings = null, [Option("no-trusted-state-validators")] bool noTrustedStateValidators = false, [Option("trusted-app-protocol-version-signer", new[] { 'T' }, Description = "Trustworthy signers who claim new app protocol versions")] string[] trustedAppProtocolVersionSigners = null, bool rpcServer = false, string rpcListenHost = "0.0.0.0", int?rpcListenPort = null, [Option("graphql-server")] bool graphQLServer = false, [Option("graphql-host")] string graphQLHost = "0.0.0.0", [Option("graphql-port")] int?graphQLPort = null, [Option("libplanet-node")] bool libplanetNode = false ) { #if SENTRY || !DEBUG try { #endif // Setup logger. var loggerConf = new LoggerConfiguration() .WriteTo.Console() .MinimumLevel.Debug(); #if SENTRY || !DEBUG loggerConf = loggerConf .WriteTo.Sentry(o => { o.InitializeSdk = false; }); #endif Log.Logger = loggerConf.CreateLogger(); if (!graphQLServer && !libplanetNode) { throw new CommandExitedException( "Either --graphql-server or --libplanet-node must be present.", -1 ); } var tasks = new List <Task>(); try { IHostBuilder graphQLHostBuilder = Host.CreateDefaultBuilder(); var standaloneContext = new StandaloneContext { KeyStore = Web3KeyStore.DefaultKeyStore, }; if (graphQLServer) { var graphQLNodeServiceProperties = new GraphQLNodeServiceProperties { GraphQLServer = graphQLServer, GraphQLListenHost = graphQLHost, GraphQLListenPort = graphQLPort, }; var graphQLService = new GraphQLService(graphQLNodeServiceProperties); graphQLHostBuilder = graphQLService.Configure(graphQLHostBuilder, standaloneContext); tasks.Add(graphQLHostBuilder.RunConsoleAsync(Context.CancellationToken)); await WaitForGraphQLService(graphQLNodeServiceProperties, Context.CancellationToken); } if (appProtocolVersionToken is null) { throw new CommandExitedException( "--app-protocol-version must be present.", -1 ); } if (genesisBlockPath is null) { throw new CommandExitedException( "--genesis-block-path must be present.", -1 ); } RpcNodeServiceProperties?rpcProperties = null; var properties = NineChroniclesNodeServiceProperties .GenerateLibplanetNodeServiceProperties( appProtocolVersionToken, genesisBlockPath, host, port, minimumDifficulty, privateKeyString, storeType, storePath, 100, iceServerStrings, peerStrings, noTrustedStateValidators, trustedAppProtocolVersionSigners, noMiner); if (rpcServer) { rpcProperties = NineChroniclesNodeServiceProperties .GenerateRpcNodeServiceProperties(rpcListenHost, rpcListenPort); properties.Render = true; } var nineChroniclesProperties = new NineChroniclesNodeServiceProperties() { Rpc = rpcProperties, Libplanet = properties }; NineChroniclesNodeService nineChroniclesNodeService = StandaloneServices.CreateHeadless(nineChroniclesProperties, standaloneContext); standaloneContext.NineChroniclesNodeService = nineChroniclesNodeService; if (libplanetNode) { if (!properties.NoMiner) { nineChroniclesNodeService.PrivateKey = properties.PrivateKey; nineChroniclesNodeService.StartMining(); } IHostBuilder nineChroniclesNodeHostBuilder = Host.CreateDefaultBuilder(); nineChroniclesNodeHostBuilder = nineChroniclesNodeService.Configure(nineChroniclesNodeHostBuilder); tasks.Add( nineChroniclesNodeHostBuilder.RunConsoleAsync(Context.CancellationToken)); } } catch (Exception e) { Log.Error(e, "Unexpected exception occurred during Run. {e}", e); } finally { await Task.WhenAll(tasks); } #if SENTRY || !DEBUG } catch (CommandExitedException) { throw; } catch (Exception exceptionToCapture) { SentrySdk.CaptureException(exceptionToCapture); throw; } #endif }
public async Task Run( [Option("app-protocol-version", new[] { 'V' }, Description = "App protocol version token")] string appProtocolVersionToken, [Option('G')] string genesisBlockPath, bool noMiner = false, [Option('H')] string?host = null, [Option('P')] ushort?port = null, [Option("swarm-private-key", Description = "The private key used for signing messages and to specify your node. " + "If you leave this null, a randomly generated value will be used.")] string?swarmPrivateKeyString = null, [Option('D')] int minimumDifficulty = 5000000, [Option("miner-private-key", Description = "The private key used for mining blocks. " + "Must not be null if you want to turn on mining with libplanet-node.")] string?minerPrivateKeyString = null, string?storeType = null, string?storePath = null, [Option("ice-server", new [] { 'I', })] string[]?iceServerStrings = null, [Option("peer")] string[]?peerStrings = null, [Option("trusted-app-protocol-version-signer", new[] { 'T' }, Description = "Trustworthy signers who claim new app protocol versions")] string[]?trustedAppProtocolVersionSigners = null, bool rpcServer = false, string rpcListenHost = "0.0.0.0", int?rpcListenPort = null, [Option("graphql-server")] bool graphQLServer = false, [Option("graphql-host")] string graphQLHost = "0.0.0.0", [Option("graphql-port")] int?graphQLPort = null, [Option("graphql-secret-token-path", Description = "The path to write GraphQL secret token. " + "If you want to protect this headless application, " + "you should use this option and take it into headers.")] string?graphQLSecretTokenPath = null, [Option(Description = "Run without CORS policy.")] bool noCors = false, [Option("workers", Description = "Number of workers to use in Swarm")] int workers = 5, [Option( "confirmations", Description = "The number of required confirmations to recognize a block. 0 by default." )] int confirmations = 0, [Option( "max-transactions", Description = "The number of maximum transactions can be included in a single block. " + "Unlimited if the value is less then or equal to 0. 100 by default." )] int maximumTransactions = 100, [Option("strict-rendering", Description = "Flag to turn on validating action renderer.")] bool strictRendering = false, [Option("dev", Description = "Flag to turn on the dev mode. false by default.")] bool isDev = false, [Option( "dev.block-interval", Description = "The time interval between blocks. It's unit is milliseconds. Works only when dev mode is on. 10000 (ms) by default.")] int blockInterval = 10000, [Option( "dev.reorg-interval", Description = "The size of reorg interval. Works only when dev mode is on. 0 by default.")] int reorgInterval = 0, [Option(Description = "Log action renders besides block renders. --rpc-server implies this.")] bool logActionRenders = false, [Option(Description = "The Cognito identity for AWS CloudWatch logging.")] string?awsCognitoIdentity = null, [Option(Description = "The access key for AWS CloudWatch logging.")] string?awsAccessKey = null, [Option(Description = "The secret key for AWS CloudWatch logging.")] string?awsSecretKey = null, [Option(Description = "The AWS region for AWS CloudWatch (e.g., us-east-1, ap-northeast-2).")] string?awsRegion = null, [Option(Description = "Run as an authorized miner, which mines only blocks that should be authorized.")] bool authorizedMiner = false, [Option(Description = "The lifetime of each transaction, which uses minute as its unit. 60 (m) by default.")] int txLifeTime = 60, [Option(Description = "The grace period for new messages, which uses second as its unit. 60 (s) by default.")] int messageTimeout = 60, [Option(Description = "The grace period for tip update, which uses second as its unit. 60 (s) by default.")] int tipTimeout = 60, [Option(Description = "A number that determines how far behind the demand the tip of the chain " + "will publish `NodeException` to GraphQL subscriptions. 1150 blocks by default.")] int demandBuffer = 1150, [Option("static-peer", Description = "A list of peers that the node will continue to maintain.")] string[]?staticPeerStrings = null, [Option("miner-count", Description = "The number of miner task(thread).")] int minerCount = 1, [Option(Description = "Run node without preloading.")] bool skipPreload = false, [Option(Description = "Minimum number of peers to broadcast message. 10 by default.")] int minimumBroadcastTarget = 10, [Option(Description = "Number of the peers can be stored in each bucket. 16 by default.")] int bucketSize = 16, [Option(Description = "Determines behavior when the chain's tip is stale. \"reboot\" and \"preload\" " + "is available and \"reboot\" option is selected by default.")] string chainTipStaleBehaviorType = "reboot" ) { #if SENTRY || !DEBUG try { #endif // Setup logger. var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); var configuration = configurationBuilder.Build(); var loggerConf = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .Destructure.UsingAttributes(); #if SENTRY || !DEBUG loggerConf = loggerConf .WriteTo.Sentry(o => { o.InitializeSdk = false; }); #endif bool useBasicAwsCredentials = !(awsAccessKey is null) && !(awsSecretKey is null); bool useCognitoCredentials = !(awsCognitoIdentity is null); if (useBasicAwsCredentials && useCognitoCredentials) { const string message = "You must choose to use only one credential between basic credential " + "(i.e., --aws-access-key, --aws-secret-key) and " + "Cognito credential (i.e., --aws-cognito-identity)."; throw new CommandExitedException(message, -1); } // Clean-up previous temporary log files. if (Directory.Exists("_logs")) { Directory.Delete("_logs", true); } if (useBasicAwsCredentials ^ useCognitoCredentials && !(awsRegion is null)) { RegionEndpoint regionEndpoint = RegionEndpoint.GetBySystemName(awsRegion); AWSCredentials credentials = useCognitoCredentials ? (AWSCredentials) new CognitoAWSCredentials(awsCognitoIdentity, regionEndpoint) : (AWSCredentials) new BasicAWSCredentials(awsAccessKey, awsSecretKey); var guid = LoadAWSSinkGuid(); if (guid is null) { guid = Guid.NewGuid(); StoreAWSSinkGuid(guid.Value); } loggerConf = loggerConf.WriteTo.AmazonS3( new AmazonS3Client(credentials, regionEndpoint), "_logs/log.json", "9c-headless-logs", formatter: new CompactJsonFormatter(), rollingInterval: Serilog.Sinks.AmazonS3.RollingInterval.Hour, batchingPeriod: TimeSpan.FromMinutes(10), batchSizeLimit: 10000, bucketPath: guid.ToString() ); } Log.Logger = loggerConf.CreateLogger(); if (!noMiner && minerPrivateKeyString is null) { throw new CommandExitedException( "--miner-private-key must be present to turn on mining at libplanet node.", -1 ); } try { IHostBuilder hostBuilder = Host.CreateDefaultBuilder(); var standaloneContext = new StandaloneContext { KeyStore = Web3KeyStore.DefaultKeyStore, }; if (graphQLServer) { string?secretToken = null; if (graphQLSecretTokenPath is { }) { var buffer = new byte[40]; new SecureRandom().NextBytes(buffer); secretToken = Convert.ToBase64String(buffer); await File.WriteAllTextAsync(graphQLSecretTokenPath, secretToken); } var graphQLNodeServiceProperties = new GraphQLNodeServiceProperties { GraphQLServer = graphQLServer, GraphQLListenHost = graphQLHost, GraphQLListenPort = graphQLPort, SecretToken = secretToken, NoCors = noCors, }; var graphQLService = new GraphQLService(graphQLNodeServiceProperties); hostBuilder = graphQLService.Configure(hostBuilder); } var properties = NineChroniclesNodeServiceProperties .GenerateLibplanetNodeServiceProperties( appProtocolVersionToken, genesisBlockPath, host, port, swarmPrivateKeyString, minimumDifficulty, storeType, storePath, 100, iceServerStrings, peerStrings, trustedAppProtocolVersionSigners, noMiner, workers: workers, confirmations: confirmations, maximumTransactions: maximumTransactions, messageTimeout: messageTimeout, tipTimeout: tipTimeout, demandBuffer: demandBuffer, staticPeerStrings: staticPeerStrings, preload: !skipPreload, minimumBroadcastTarget: minimumBroadcastTarget, bucketSize: bucketSize, chainTipStaleBehaviorType: chainTipStaleBehaviorType ); if (rpcServer) { properties.Render = true; properties.LogActionRenders = true; } if (logActionRenders) { properties.LogActionRenders = true; } var minerPrivateKey = string.IsNullOrEmpty(minerPrivateKeyString) ? null : new PrivateKey(ByteUtil.ParseHex(minerPrivateKeyString)); var nineChroniclesProperties = new NineChroniclesNodeServiceProperties() { MinerPrivateKey = minerPrivateKey, Libplanet = properties, Dev = isDev, StrictRender = strictRendering, BlockInterval = blockInterval, ReorgInterval = reorgInterval, AuthorizedMiner = authorizedMiner, TxLifeTime = TimeSpan.FromMinutes(txLifeTime), MinerCount = minerCount, }; hostBuilder.ConfigureServices(services => { services.AddSingleton(_ => standaloneContext); }); hostBuilder.UseNineChroniclesNode(nineChroniclesProperties, standaloneContext); if (rpcServer) { hostBuilder.UseNineChroniclesRPC( NineChroniclesNodeServiceProperties .GenerateRpcNodeServiceProperties(rpcListenHost, rpcListenPort) ); } await hostBuilder.RunConsoleAsync(Context.CancellationToken); }
public static async Task Main() { // Get configuration var configurationBuilder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddEnvironmentVariables("NC_"); IConfiguration config = configurationBuilder.Build(); var headlessConfig = new Configuration(); config.Bind(headlessConfig); var loggerConf = new LoggerConfiguration() .ReadFrom.Configuration(config); Log.Logger = loggerConf.CreateLogger(); // FIXME: quick and dirty workaround. // Please remove it after fixing Libplanet.Net.Swarm<T> and NetMQTransport... if (string.IsNullOrEmpty(headlessConfig.Host)) { headlessConfig.Host = null; } var context = new StandaloneContext { KeyStore = Web3KeyStore.DefaultKeyStore, }; IHostBuilder hostBuilder = Host.CreateDefaultBuilder(); CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; hostBuilder.ConfigureWebHostDefaults(builder => { builder.UseStartup <GraphQL.GraphQLStartup>(); builder.UseUrls($"http://{headlessConfig.GraphQLHost}:{headlessConfig.GraphQLPort}/"); }); var properties = NineChroniclesNodeServiceProperties .GenerateLibplanetNodeServiceProperties( headlessConfig.AppProtocolVersionToken, headlessConfig.GenesisBlockPath, headlessConfig.Host, headlessConfig.Port, headlessConfig.SwarmPrivateKeyString, headlessConfig.StoreType, headlessConfig.StorePath, 100, headlessConfig.IceServerStrings, headlessConfig.PeerStrings, headlessConfig.TrustedAppProtocolVersionSigners, noMiner: true, workers: headlessConfig.Workers, confirmations: headlessConfig.Confirmations, messageTimeout: headlessConfig.MessageTimeout, tipTimeout: headlessConfig.TipTimeout, demandBuffer: headlessConfig.DemandBuffer, minimumBroadcastTarget: headlessConfig.MinimumBroadcastTarget, bucketSize: headlessConfig.BucketSize, staticPeerStrings: headlessConfig.StaticPeerStrings, render: true, preload: false, transportType: "netmq"); var nineChroniclesProperties = new NineChroniclesNodeServiceProperties() { MinerPrivateKey = null, Libplanet = properties, TxQuotaPerSigner = 10, }; if (headlessConfig.LogActionRenders) { properties.LogActionRenders = true; } hostBuilder.UseNineChroniclesNode(nineChroniclesProperties, context); // ConfigureServices must come before Configure for now hostBuilder = hostBuilder .ConfigureServices((ctx, services) => { services.AddDbContextFactory <NineChroniclesContext>( options => options.UseMySQL(headlessConfig.MySqlConnectionString) ); services.AddHostedService <RenderSubscriber>(); services.AddSingleton <MySqlStore>(); services.Configure <Configuration>(config); }); await hostBuilder.RunConsoleAsync(token); }
public ValidationQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <BooleanGraphType> >( name: "metadata", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "raw", Description = "The raw value of json metadata." }), resolve: context => { var raw = context.GetArgument <string>("raw"); try { Log.Debug($"Validating received raw: {raw}"); // FIXME: Thread.Sleep is temporary. Should be removed. var timeSpent = 0; const int retryInterval = 1000; const int grace = 100 * 1000; while (standaloneContext.BlockChain is null) { Log.Debug( "Blockchain instance is null. Sleep {interval}ms...", retryInterval); Thread.Sleep(retryInterval); timeSpent += retryInterval; if (timeSpent < grace) { continue; } var msg = $"Blockchain instance is not initialized until {grace}ms."; Log.Debug(msg); throw new BlockChainInitializeException(msg); } Log.Debug("Time until blockchain online: {time}ms", timeSpent); var remoteIndex = JsonDocument.Parse(raw).RootElement.GetProperty("Index").GetInt32(); Log.Debug("Remote: {index1}, Local: {index2}", remoteIndex, standaloneContext.BlockChain.Tip.Index); var ret = remoteIndex > standaloneContext.BlockChain.Tip.Index; return(ret); } catch (JsonException je) { Log.Warning(je, "Given metadata is invalid. (raw: {raw})", raw); return(false); } catch (Exception e) { var msg = $"Exception occurred while validating metadata. (raw: {raw})"; Log.Warning(e, msg + " {e}", e); throw new ExecutionError(msg, e); } } ); Field <NonNullGraphType <BooleanGraphType> >( name: "privateKey", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <ByteStringType> > { Name = "hex", Description = "The raw value of private-key, presented as hexadecimal." }), resolve: context => { try { var rawPrivateKey = context.GetArgument <byte[]>("hex"); var _ = new PrivateKey(rawPrivateKey); return(true); } catch (ArgumentException) { return(false); } } ); Field <NonNullGraphType <BooleanGraphType> >( name: "publicKey", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <ByteStringType> > { Name = "hex", Description = "The raw value of public-key, presented as hexadecimal." }), resolve: context => { try { var rawPublicKey = context.GetArgument <byte[]>("hex"); var _ = new PublicKey(rawPublicKey); return(true); } catch (ArgumentException) { return(false); } catch (FormatException) { return(false); } } ); }
public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration configuration) { bool useSecretToken = configuration[GraphQLService.SecretTokenKey] is { }; Field <NonNullGraphType <StateQuery> >(name: "stateQuery", arguments: new QueryArguments( new QueryArgument <ByteStringType> { Name = "hash", Description = "Offset block hash for query.", }), resolve: context => { BlockHash?blockHash = context.GetArgument <byte[]>("hash") switch { byte[] bytes => new BlockHash(bytes), null => null, }; return(standaloneContext.BlockChain?.ToAccountStateGetter(blockHash), standaloneContext.BlockChain?.ToAccountBalanceGetter(blockHash)); } ); Field <ByteStringType>( name: "state", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "address", Description = "The address of state to fetch from the chain." }, new QueryArgument <ByteStringType> { Name = "hash", Description = "The hash of the block used to fetch state from chain." } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } var address = context.GetArgument <Address>("address"); var blockHashByteArray = context.GetArgument <byte[]>("hash"); var blockHash = blockHashByteArray is null ? blockChain.Tip.Hash : new BlockHash(blockHashByteArray); var state = blockChain.GetState(address, blockHash); return(new Codec().Encode(state)); } ); Field <NonNullGraphType <ListGraphType <NonNullGraphType <TransferNCGHistoryType> > > >( "transferNCGHistories", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <ByteStringType> > { Name = "blockHash" }, new QueryArgument <AddressType> { Name = "recipient" } ), resolve: context => { BlockHash blockHash = new BlockHash(context.GetArgument <byte[]>("blockHash")); if (!(standaloneContext.Store is { } store)) { throw new InvalidOperationException(); } if (!(store.GetBlock <NCAction>(blockHash) is { } block)) { throw new ArgumentException("blockHash"); } var recipient = context.GetArgument <Address?>("recipient"); var txs = block.Transactions.Where(tx => tx.Actions.Count == 1 && tx.Actions.First().InnerAction is TransferAsset transferAsset && (!recipient.HasValue || transferAsset.Recipient == recipient) && transferAsset.Amount.Currency.Ticker == "NCG" && store.GetTxExecution(blockHash, tx.Id) is TxSuccess); TransferNCGHistory ToTransferNCGHistory(TxSuccess txSuccess) { var rawTransferNcgHistories = txSuccess.FungibleAssetsDelta.Select(pair => (pair.Key, pair.Value.Values.First(fav => fav.Currency.Ticker == "NCG"))) .ToArray(); var((senderAddress, _), (recipientAddress, amount)) = rawTransferNcgHistories[0].Item2.RawValue > rawTransferNcgHistories[1].Item2.RawValue ? (rawTransferNcgHistories[1], rawTransferNcgHistories[0]) : (rawTransferNcgHistories[0], rawTransferNcgHistories[1]); return(new TransferNCGHistory( txSuccess.BlockHash, txSuccess.TxId, senderAddress, recipientAddress, amount)); } var histories = txs.Select(tx => ToTransferNCGHistory((TxSuccess)store.GetTxExecution(blockHash, tx.Id))); return(histories); });
public GraphQLController(StandaloneContext standaloneContext) { StandaloneContext = standaloneContext; }
public NodeStatusType(StandaloneContext context) { Field <NonNullGraphType <BooleanGraphType> >( name: "bootstrapEnded", description: "Whether the current libplanet node has ended bootstrapping.", resolve: _ => context.BootstrapEnded ); Field <NonNullGraphType <BooleanGraphType> >( name: "preloadEnded", description: "Whether the current libplanet node has ended preloading.", resolve: _ => context.PreloadEnded ); Field <NonNullGraphType <BlockHeaderType> >( name: "tip", description: "Block header of the tip block from the current canonical chain.", resolve: _ => context.BlockChain is { } blockChain ? BlockHeaderType.FromBlock(blockChain.Tip) : null ); Field <NonNullGraphType <ListGraphType <BlockHeaderType> > >( name: "topmostBlocks", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <IntGraphType> > { Name = "limit", Description = "The number of blocks to get." }, new QueryArgument <AddressType> { Name = "miner", Description = "List only blocks mined by the given address. " + "(List everything if omitted.)", DefaultValue = null, } ), description: "The topmost blocks from the current node.", resolve: fieldContext => { if (context.BlockChain is null) { throw new InvalidOperationException($"{nameof(context.BlockChain)} is null."); } IEnumerable <Block <NCAction> > blocks = GetTopmostBlocks(context.BlockChain); if (fieldContext.GetArgument <Address?>("miner") is { } miner) { blocks = blocks.Where(b => b.Miner.Equals(miner)); } return(blocks .Take(fieldContext.GetArgument <int>("limit")) .Select(BlockHeaderType.FromBlock)); }); Field <ListGraphType <TxIdType> >( name: "stagedTxIds", arguments: new QueryArguments( new QueryArgument <AddressType> { Name = "address", Description = "Target address to query" } ), description: "Ids of staged transactions from the current node.", resolve: fieldContext => { if (context.BlockChain is null) { throw new InvalidOperationException($"{nameof(context.BlockChain)} is null."); } if (!fieldContext.HasArgument("address")) { return(context.BlockChain.GetStagedTransactionIds()); } else { Address address = fieldContext.GetArgument <Address>("address"); IImmutableSet <TxId> stagedTransactionIds = context.BlockChain.GetStagedTransactionIds(); return(stagedTransactionIds.Where(txId => context.BlockChain.GetTransaction(txId).Signer.Equals(address))); } } ); Field <NonNullGraphType <BlockHeaderType> >( name: "genesis", description: "Block header of the genesis block from the current chain.", resolve: fieldContext => context.BlockChain is { } blockChain ? BlockHeaderType.FromBlock(blockChain.Genesis) : null ); Field <NonNullGraphType <BooleanGraphType> >( name: "isMining", description: "Whether the current node is mining.", resolve: _ => context.IsMining ); }
public async Task Run( bool noMiner = false, [Option("app-protocol-version", new[] { 'V' }, Description = "App protocol version token")] string appProtocolVersionToken = null, [Option('G')] string genesisBlockPath = null, [Option('H')] string host = null, [Option('P')] ushort?port = null, [Option('D')] int minimumDifficulty = 5000000, [Option("private-key")] string privateKeyString = null, string storeType = null, string storePath = null, [Option("ice-server", new [] { 'I', })] string[] iceServerStrings = null, [Option("peer")] string[] peerStrings = null, [Option("no-trusted-state-validators")] bool noTrustedStateValidators = false, [Option("trusted-app-protocol-version-signer", new[] { 'T' }, Description = "Trustworthy signers who claim new app protocol versions")] string[] trustedAppProtocolVersionSigners = null, bool rpcServer = false, string rpcListenHost = "0.0.0.0", int?rpcListenPort = null, [Option("graphql-server")] bool graphQLServer = false, [Option("graphql-host")] string graphQLHost = "0.0.0.0", [Option("graphql-port")] int?graphQLPort = null, [Option("libplanet-node")] bool libplanetNode = false, [Option("workers", Description = "Number of workers to use in Swarm")] int workers = 5, [Option( "confirmations", Description = "The number of required confirmations to recognize a block. 0 by default." )] int confirmations = 0, [Option( "max-transactions", Description = "The number of maximum transactions can be included in a single block. " + "Unlimited if the value is less then or equal to 0. 100 by default." )] int maximumTransactions = 100, [Option("strict-rendering", Description = "Flag to turn on validating action renderer.")] bool strictRendering = false, [Option("dev", Description = "Flag to turn on the dev mode. false by default.")] bool isDev = false, [Option( "dev.block-interval", Description = "The time interval between blocks. It's unit is milliseconds. Works only when dev mode is on. 10000 (ms) by default.")] int blockInterval = 10000, [Option( "dev.reorg-interval", Description = "The size of reorg interval. Works only when dev mode is on. 0 by default.")] int reorgInterval = 0, [Option(Description = "The log minimum level during headless execution. debug by default.")] string logMinimumLevel = "debug", [Option(Description = "The Cognito identity for AWS CloudWatch logging.")] string awsCognitoIdentity = null, [Option(Description = "The access key for AWS CloudWatch logging.")] string awsAccessKey = null, [Option(Description = "The secret key for AWS CloudWatch logging.")] string awsSecretKey = null, [Option(Description = "The AWS region for AWS CloudWatch (e.g., us-east-1, ap-northeast-2).")] string awsRegion = null ) { #if SENTRY || !DEBUG try { #endif // Setup logger. var loggerConf = new LoggerConfiguration() .WriteTo.Console(outputTemplate: LogTemplate) .ConfigureMinimumLevel(logMinimumLevel); #if SENTRY || !DEBUG loggerConf = loggerConf .WriteTo.Sentry(o => { o.InitializeSdk = false; }); #endif bool useBasicAwsCredentials = !(awsAccessKey is null) && !(awsSecretKey is null); bool useCognitoCredentials = !(awsCognitoIdentity is null); if (useBasicAwsCredentials && useCognitoCredentials) { const string message = "You must choose to use only one credential between basic credential " + "(i.e., --aws-access-key, --aws-secret-key) and " + "Cognito credential (i.e., --aws-cognito-identity)."; throw new CommandExitedException(message, -1); } if (useBasicAwsCredentials ^ useCognitoCredentials && !(awsRegion is null)) { var regionEndpoint = RegionEndpoint.GetBySystemName(awsRegion); AWSCredentials credentials = useCognitoCredentials ? (AWSCredentials) new CognitoAWSCredentials(awsCognitoIdentity, regionEndpoint) : (AWSCredentials) new BasicAWSCredentials(awsAccessKey, awsSecretKey); var guid = LoadAWSSinkGuid() ?? Guid.NewGuid(); StoreAWSSinkGuid(guid); var awsSink = new AWSSink( credentials, regionEndpoint, "9c-standalone-logs", guid.ToString()); loggerConf.WriteTo.Sink(awsSink); } Log.Logger = loggerConf.CreateLogger(); if (!graphQLServer && !libplanetNode) { throw new CommandExitedException( "Either --graphql-server or --libplanet-node must be present.", -1 ); } var tasks = new List <Task>(); try { IHostBuilder graphQLHostBuilder = Host.CreateDefaultBuilder(); var standaloneContext = new StandaloneContext { KeyStore = Web3KeyStore.DefaultKeyStore, }; if (graphQLServer) { var graphQLNodeServiceProperties = new GraphQLNodeServiceProperties { GraphQLServer = graphQLServer, GraphQLListenHost = graphQLHost, GraphQLListenPort = graphQLPort, }; var graphQLService = new GraphQLService(graphQLNodeServiceProperties); graphQLHostBuilder = graphQLService.Configure(graphQLHostBuilder, standaloneContext); tasks.Add(graphQLHostBuilder.RunConsoleAsync(Context.CancellationToken)); await WaitForGraphQLService(graphQLNodeServiceProperties, Context.CancellationToken); } if (appProtocolVersionToken is null) { throw new CommandExitedException( "--app-protocol-version must be present.", -1 ); } if (genesisBlockPath is null) { throw new CommandExitedException( "--genesis-block-path must be present.", -1 ); } RpcNodeServiceProperties?rpcProperties = null; var properties = NineChroniclesNodeServiceProperties .GenerateLibplanetNodeServiceProperties( appProtocolVersionToken, genesisBlockPath, host, port, minimumDifficulty, privateKeyString, storeType, storePath, 100, iceServerStrings, peerStrings, noTrustedStateValidators, trustedAppProtocolVersionSigners, noMiner, workers: workers, confirmations: confirmations, maximumTransactions: maximumTransactions); if (rpcServer) { rpcProperties = NineChroniclesNodeServiceProperties .GenerateRpcNodeServiceProperties(rpcListenHost, rpcListenPort); properties.Render = true; } var nineChroniclesProperties = new NineChroniclesNodeServiceProperties() { Rpc = rpcProperties, Libplanet = properties }; NineChroniclesNodeService nineChroniclesNodeService = StandaloneServices.CreateHeadless( nineChroniclesProperties, standaloneContext, strictRendering: strictRendering, isDev: isDev, blockInterval: blockInterval, reorgInterval: reorgInterval); standaloneContext.NineChroniclesNodeService = nineChroniclesNodeService; if (libplanetNode) { if (!properties.NoMiner) { nineChroniclesNodeService.PrivateKey = properties.PrivateKey; nineChroniclesNodeService.StartMining(); } IHostBuilder nineChroniclesNodeHostBuilder = Host.CreateDefaultBuilder(); nineChroniclesNodeHostBuilder = nineChroniclesNodeService.Configure(nineChroniclesNodeHostBuilder); tasks.Add( nineChroniclesNodeHostBuilder.RunConsoleAsync(Context.CancellationToken)); } await Task.WhenAll(tasks); } catch (TaskCanceledException) { Log.Information("Terminated by the cancellation."); } catch (Exception e) { Log.Error(e, "Unexpected exception occurred during Run. {e}", e); } #if SENTRY || !DEBUG } catch (CommandExitedException) { throw; } catch (Exception exceptionToCapture) { SentrySdk.CaptureException(exceptionToCapture); throw; } #endif }
public TransactionHeadlessQuery(StandaloneContext standaloneContext) { Field <NonNullGraphType <LongGraphType> >( name: "nextTxNonce", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <AddressType> > { Name = "address", Description = "Target address to query" } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } Address address = context.GetArgument <Address>("address"); return(blockChain.GetNextTxNonce(address)); } ); Field <TransactionType <NCAction> >( name: "getTx", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <TxIdType> > { Name = "txId", Description = "transaction id." } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } var txId = context.GetArgument <TxId>("txId"); return(blockChain.GetTransaction(txId)); } ); Field <NonNullGraphType <StringGraphType> >( name: "createUnsignedTx", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "publicKey", Description = "The base64-encoded public key for Transaction.", }, new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "plainValue", Description = "The base64-encoded plain value of action for Transaction.", } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } string plainValueString = context.GetArgument <string>("plainValue"); var plainValue = new Bencodex.Codec().Decode(System.Convert.FromBase64String(plainValueString)); #pragma warning disable 612 var action = new NCAction(); #pragma warning restore 612 action.LoadPlainValue(plainValue); var publicKey = new PublicKey(Convert.FromBase64String(context.GetArgument <string>("publicKey"))); Address signer = publicKey.ToAddress(); long nonce = blockChain.GetNextTxNonce(signer); Transaction <NCAction> unsignedTransaction = Transaction <NCAction> .CreateUnsigned(nonce, publicKey, blockChain.Genesis.Hash, new[] { action }); return(Convert.ToBase64String(unsignedTransaction.Serialize(false))); }); Field <NonNullGraphType <StringGraphType> >( name: "attachSignature", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "unsignedTransaction", Description = "The base64-encoded unsigned transaction to attach the given signature." }, new QueryArgument <NonNullGraphType <StringGraphType> > { Name = "signature", Description = "The base64-encoded signature of the given unsigned transaction." } ), resolve: context => { byte[] signature = Convert.FromBase64String(context.GetArgument <string>("signature")); Transaction <NCAction> unsignedTransaction = Transaction <NCAction> .Deserialize( Convert.FromBase64String(context.GetArgument <string>("unsignedTransaction")), false); Transaction <NCAction> signedTransaction = new Transaction <NCAction>( unsignedTransaction.Nonce, unsignedTransaction.Signer, unsignedTransaction.PublicKey, unsignedTransaction.GenesisHash, unsignedTransaction.UpdatedAddresses, unsignedTransaction.Timestamp, unsignedTransaction.Actions, signature); return(Convert.ToBase64String(signedTransaction.Serialize(true))); }); Field <NonNullGraphType <TxResultType> >( name: "transactionResult", arguments: new QueryArguments( new QueryArgument <NonNullGraphType <TxIdType> > { Name = "txId", Description = "transaction id." } ), resolve: context => { if (!(standaloneContext.BlockChain is BlockChain <PolymorphicAction <ActionBase> > blockChain)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } if (!(standaloneContext.Store is IStore store)) { throw new ExecutionError( $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.Store)} was not set yet!"); } TxId txId = context.GetArgument <TxId>("txId"); if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) { return(blockChain.GetStagedTransactionIds().Contains(txId) ? new TxResult(TxStatus.STAGING, null, null) : new TxResult(TxStatus.INVALID, null, null)); } try { TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); Block <PolymorphicAction <ActionBase> > txExecutedBlock = blockChain[txExecutedBlockHash]; return(execution switch { TxSuccess txSuccess => new TxResult(TxStatus.SUCCESS, txExecutedBlock.Index, txExecutedBlock.Hash.ToString()), TxFailure txFailure => new TxResult(TxStatus.FAILURE, txExecutedBlock.Index, txExecutedBlock.Hash.ToString()), _ => throw new NotImplementedException( $"{nameof(execution)} is not expected concrete class.") }); } catch (Exception) { return(new TxResult(TxStatus.INVALID, null, null)); } }