C# .NET 5对接Tendermint GRPC出现RST_STREAM PROTOCOL_ERROR错误
Tendermint接入C# gRPC ABCI服务出现PROTOCOL_ERROR错误
我在项目中需要使用Tendermint,此前没有相关使用经验,打算先参照官方教程在C#(.NET 5.0)环境下实现最简示例,最小示例包下载地址:Minimal Example
我尽可能参照教程实现了简单GRPC服务:
Startup.cs代码
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration conf) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<KVStoreService>(); endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); }); }); app.UseGlobalHostEnvironemnt(env); // 测试相关代码与当前问题无关 app.UseGlobalConfiguration(conf); app.UseGlobalLogger(); app.UseTendermint(); // 启动tendermint进程 } }
我通过基础的Process.Start调用启动Tendermint,.toml配置文件使用默认值,已修正官方文档拼写错误,使用下划线格式的--proxy_app参数(错误日志已明确提示该问题):
public static void ConfigureTendermint() { var tendermint = Process.Start(new ProcessStartInfo { FileName = @"Tendermint\tendermint.exe", Arguments = "init validator --home=Tendermint" }); tendermint?.WaitForExit(); Process.Start(new ProcessStartInfo { FileName = @"Tendermint\tendermint.exe", Arguments = @"node --abci grpc --proxy_app tcp://127.0.0.1:5020 --home=Tendermint --log_level debug" }); }
如下是项目文件,已配置.proto文件处理规则,所有proto文件均可正常生成可用代码:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Grpc" Version="2.39.1" /> <PackageReference Include="Grpc.AspNetCore" Version="2.39.0" /> <PackageReference Include="Grpc.Tools" Version="2.39.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="LiteDB" Version="5.0.11" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\CommonLib.AspNet\CommonLib.AspNet\CommonLib.AspNet.csproj" /> <ProjectReference Include="..\..\CommonLib\CommonLib\CommonLib.csproj" /> </ItemGroup> <ItemGroup> <Protobuf Include="Source\Protos\gogoproto\gogo.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\crypto\keys.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\crypto\proof.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\block.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\canonical.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\events.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\evidence.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\params.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\types.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\types\validator.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\version\types.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> <Protobuf Include="Source\Protos\tendermint\abci\types.proto" GrpcServices="Server" ProtoRoot="Source/Protos" /> </ItemGroup> <ItemGroup> <Folder Include="Source\Database\" /> </ItemGroup> </Project>
如下是服务实现代码,已将前文提到的官方示例翻译为C#版本:
public class KVStoreService : ABCIApplication.ABCIApplicationBase { private static LiteDatabase _env; private static ILiteCollection<KV> _store; public LiteDatabase Env => _env ??= new LiteDatabase(WebUtils.Configuration?.GetConnectionString("LiteDb")); public ILiteCollection<KV> Store => _store ??= Env.GetCollection<KV>("kvs"); public override Task<ResponseEcho> Echo(RequestEcho request, ServerCallContext context) { return Task.FromResult(new ResponseEcho { Message = $"Validator is Running: {DateTime.Now:dd-MM-yyyy HH:mm}" }); } public override Task<ResponseCheckTx> CheckTx(RequestCheckTx request, ServerCallContext context) { var (code, _) = Validate(request.Tx); return Task.FromResult(new ResponseCheckTx { Code = code, GasWanted = 1 }); } public override Task<ResponseBeginBlock> BeginBlock(RequestBeginBlock request, ServerCallContext context) { Env.BeginTrans(); Store.EnsureIndex(x => x.Key, true); return Task.FromResult(new ResponseBeginBlock()); } public override Task<ResponseDeliverTx> DeliverTx(RequestDeliverTx request, ServerCallContext context) { var (code, kv) = Validate(request.Tx); if (code == 0) Store.Insert(kv); return Task.FromResult(new ResponseDeliverTx { Code = code }); } public override Task<ResponseCommit> Commit(RequestCommit request, ServerCallContext context) { Env.Commit(); return Task.FromResult(new ResponseCommit { Data = ByteString.CopyFrom(new byte[8]) }); } public override Task<ResponseQuery> Query(RequestQuery request, ServerCallContext context) { var k = request.Data.ToBase64(); var v = Store.FindOne(x => x.Key == k)?.Value; var resp = new ResponseQuery(); if (v == null) resp.Log = $"There is no value for \"{k}\" key"; else { resp.Log = "KVP:"; resp.Key = ByteString.FromBase64(k); resp.Value = ByteString.FromBase64(v); } return Task.FromResult(resp); } private (uint, KV) Validate(ByteString tx) { var kv = tx.ToStringUtf8().Split('=').Select(kv => kv.UTF8ToBase64()).ToKV(); if (kv.Key.IsNullOrWhiteSpace() || kv.Value.IsNullOrWhiteSpace()) return (1, kv); var stored = Store.FindOne(x => x.Key == kv.Key)?.Value; if (stored != null && stored == kv.Value) return (2, kv); return (0, kv); } }
我自行实现了简单的服务客户端,同时启动服务端和客户端可以完美运行:
public class Program { public static async Task Main() { LoggerUtils.Logger.Log(LogLevel.Info, $@"Log Path: {LoggerUtils.LogPath}"); using var channel = GrpcChannel.ForAddress("http://localhost:5020"); var client = new ABCIApplication.ABCIApplicationClient(channel); var echo = string.Empty; while (echo.IsNullOrWhiteSpace()) { try { echo = client.Echo(new RequestEcho()).Message; } catch (Exception ex) when (ex is HttpRequestException or RpcException) { await Task.Delay(1000); LoggerUtils.Logger.Log(LogLevel.Info, "Server not Ready, retrying..."); } } var beginBlock = await client.BeginBlockAsync(new RequestBeginBlock()); var deliver = await client.DeliverTxAsync(new RequestDeliverTx { Tx = ByteString.CopyFromUtf8("tendermint=rocks") }); var commit = await client.CommitAsync(new RequestCommit()); var checkTx = await client.CheckTxAsync(new RequestCheckTx { Tx = ByteString.CopyFromUtf8("tendermint=rocks") }); var query = await client.QueryAsync(new RequestQuery { Data = ByteString.CopyFromUtf8("tendermint") }); System.Console.WriteLine($"Echo Status: {echo}"); System.Console.WriteLine($"Begin Block Status: {(beginBlock != null ? "Success" : "Failure")}"); System.Console.WriteLine($"Delivery Status: {deliver.Code switch { 0 => "Success", 1 => "Invalid Data", 2 => "Already Exists", _ => "Failure" }}"); System.Console.WriteLine($"Commit Status: {(commit.Data.ToByteArray().SequenceEqual(new byte[8]) ? "Success" : "Failure")}"); System.Console.WriteLine($"CheckTx Status: {checkTx.Code switch { 0 => "Success", 1 => "Invalid Data", 2 => "Already Exists", _ => "Failure" }}"); System.Console.WriteLine($"Query Status: {query.Log} {query.Key.ToStringUtf8()}{(query.Key == ByteString.Empty || query.Value == ByteString.Empty ? "" : "=")}{query.Value.ToStringUtf8()}"); System.Console.ReadKey(); } }
到这一步运行都正常,但我将Tendermint接入链路后(让客户端可以通过Tendermint调用我的应用),就会出现如下报错:
Echo failed - module=abci-client connection=query err="rpc error: code = Internal desc = stream terminated by RST_STREAM with error code: PROTOCOL_ERROR"
无论我用什么方式启动进程,该错误都会重复出现。显然因为Tendermint无法连接到代理应用,所以也无法被调用。
我已经将日志等级设为Debug(包括GRPC日志),仅能获取到上述错误信息。类似的技术帖提到该问题可能和元数据有关(比如元数据中包含\n),但我不清楚该原因是否和我的场景相关。
我判断应该是某个简单的配置错误导致Tendermint无法和实际应用通信,但我无法定位问题,恳请各位提供帮助。
内容的提问来源于stack exchange,提问作者rvnlord
相关产品推荐
相关产品推荐

