From fca7566228a5ad7c11fbc4ca02c3dd045f27bc2b Mon Sep 17 00:00:00 2001 From: b00f Date: Sat, 20 Jul 2024 21:22:27 +0800 Subject: [PATCH 1/6] ci(linter): mandatory scope for PR and commit messages (#1426) --- .github/workflows/semantic-pr.yml | 51 ++++++++++++++++++ CONTRIBUTING.md | 88 ++++++++++++++++++++----------- 2 files changed, 109 insertions(+), 30 deletions(-) diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml index a68fe8823..ae6f84ed9 100644 --- a/.github/workflows/semantic-pr.yml +++ b/.github/workflows/semantic-pr.yml @@ -6,6 +6,10 @@ on: - opened - edited - synchronize + - reopened + +permissions: + pull-requests: read jobs: main: @@ -15,3 +19,50 @@ jobs: - uses: amannn/action-semantic-pull-request@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which scopes are allowed (newline-delimited). + scopes: | + github + linter + deps + makefile + release + other + daemon + cmd + gtk + shell + wallet-cmd + committee + config + consensus + crypto + docs + execution + genesis + network + node + sandbox + scripts + sortition + state + store + sync + main + txpool + types + util + version + wallet + grpc + http + jsonrpc + nanomsg + # Configure that a scope must always be provided. + requireScope: true + # The subject should not start with an uppercase character and should not end with a period. + subjectPattern: ^(?![A-Z]).+[^.]$ + subjectPatternError: | + The subject "{subject}" cannot start with an uppercase character or end with a period. + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68ccaf68d..b572c4da8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,51 +1,62 @@ # Contributing -Thank you for considering contributing to Pactus blockchain! +Thank you for considering contributing to the Pactus blockchain! Please read these guidelines before submitting a pull request or opening an issue. ## Code Guidelines -We strive to maintain clean, readable, and maintainable code in Pactus blockchain. -Please follow these guidelines when contributing code to the project: +We strive to maintain clean, readable, and maintainable code in the Pactus blockchain. +Please follow these guidelines when contributing to the project: -- Code should follow the [Effective Go](https://golang.org/doc/effective_go.html) guidelines. -- Documentation should follow the [Go Doc Comments](https://go.dev/doc/comment) format. -- Follow the principles of clean code as outlined in Robert C. Martin's "[Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)" book. Here you can find a [summary](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29) of the book. +- Follow the [Effective Go](https://golang.org/doc/effective_go.html) guidelines. +- Follow the [Go Doc Comments](https://go.dev/doc/comment) guidelines. +- Follow the principles of clean code as outlined in + Robert C. Martin's "[Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)" book. - Write tests for new code or changes to existing code, and make sure all tests pass before submitting a pull request. -- Error strings and log messages should not be capitalized (unless beginning with proper nouns or acronyms) and - SHOULD NOT end with punctuation. Examples: - * Correct ✅: "unable to connect to server" - * Incorrect ❌: "Unable to connect to server" - * Incorrect ❌: "unable to connect to server." -The following commands are available in the Makefile: +### Makefile Targets + +You can use these commands in the Makefile: - `make build` compiles the code into executable binaries. - `make devtools` installs required development tools. - `make fmt` formats the code according to the Go standards. -- `make check` runs various checks on the code, including formatting and linting. -- `make test` performs all the tests including unit tests and system tests. -- `make uint_test` performs only unit tests. -- `make proto` generates [protobuf](https://protobuf.dev/) files, if you have made any changes to the proto buffer files. +- `make check` runs checks on the code, including formatting and linting. +- `make test` runs all the tests, including unit tests and system tests. +- `make unit_test` runs only unit tests. +- `make proto` generates [protobuf](https://protobuf.dev/) files. + Run this target if you have made any changes to the proto buffer files. + +### Error and Log Messages + +Error and log messages should not start with a capital letter (unless it's a proper noun or acronym) and +should not end with punctuation. -## CLI Guidelines +#### Examples -The help messages for CLI flags should follow this pattern: +- Correct ✅: "unable to connect to server" +- Incorrect ❌: "Unable to connect to server" +- Incorrect ❌: "unable to connect to server." -- Start all messages with a lowercase letter. -- Avoid stating defaults in the help string, as Cobra automatically adds them. -- Include a range for flags that accept a range of values. +### Help Messages + +Follow these rules for help messages for CLI commands and flags: + +- Help string should not start with a capital letter. +- Help string should not end with punctuation. +- Don't include default value in the help string. +- Include the acceptable range for the flags that accept a range of values. ## Commit Guidelines -Please follow these guidelines when committing changes to Pactus blockchain: +Please follow these rules when committing changes to the Pactus blockchain: - Each commit should represent a single, atomic change to the codebase. Avoid making multiple unrelated changes in a single commit. -- Commit message should not be longer than 50 characters. -- Commit message should start with lowercase and not ends with punctuation. -- Write commit message in the imperative: "fix bug" and not "fixed bug" or "fixes bug". -- Use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format for commit messages and Pull Request titles. +- Use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format for commit messages and + Pull Request titles. + +### Commit type List of conventional commit [types](https://github.com/commitizen/conventional-commit-types/blob/master/index.json): @@ -62,16 +73,33 @@ List of conventional commit [types](https://github.com/commitizen/conventional-c | style | Changes that do not affect the meaning of the code (white-space, formatting, etc) | | chore | Other changes that don't modify src or test files | -### Example fo commit messages +### Commit Scope + +The scope helps specify which part of the code is affected by your commit. +It must be included in the commit message to provide clarity. +Multiple scopes can be used if the changes impact several areas. + +Here’s a list of available scopes: [available scopes](./.github/workflows/semantic-pr.yml). + +### Commit Description + +- Keep the commit message under 50 characters. +- Start the commit message with a lowercase letter and do not end with punctuation. +- Write commit messages in the imperative: "fix bug" not "fixed bug" or "fixes bug". + +### Examples - - Correct ✅: "feat(gRPC): sign transaction using wallet client" + - Correct ✅: "feat(grpc): sign transaction using wallet client" + - Correct ✅: "feat(grpc, wallet): sign transaction using wallet client" - Incorrect ❌: 'feat(gRPC): Sign transaction using wallet client." - - Incorrect ❌: 'feat(gRPC): signed transaction using wallet client" + - Incorrect ❌: 'feat(grpc): Sign transaction using wallet client." + - Incorrect ❌: 'feat(grpc): signed transaction using wallet client" - Incorrect ❌: 'sign transaction using wallet client" ## Code of Conduct -This project has adapted the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) +This project has adapted the +[Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) to ensure that our community is welcoming and inclusive for all. Please read it before contributing to the project. From 757f6ce9fb9ec0a1fdc068390de659c4cd4d25b0 Mon Sep 17 00:00:00 2001 From: Javad Rajabzadeh Date: Sun, 21 Jul 2024 11:26:10 +0330 Subject: [PATCH 2/6] fix(grpc): show negative pruning height when is pruned false (#1429) --- www/grpc/blockchain.go | 7 ++++++- www/grpc/gen/dart/blockchain.pb.dart | 4 ++-- www/grpc/gen/dart/blockchain.pbjson.dart | 4 ++-- www/grpc/gen/docs/grpc.md | 2 +- www/grpc/gen/go/blockchain.pb.go | 6 +++--- .../blockchain/BlockchainOuterClass.java | 18 +++++++++--------- www/grpc/gen/js/blockchain_pb.js | 6 +++--- www/grpc/gen/python/blockchain_pb2.py | 2 +- www/grpc/gen/rust/pactus.rs | 4 ++-- www/grpc/proto/blockchain.proto | 2 +- www/grpc/swagger-ui/pactus.swagger.json | 2 +- 11 files changed, 31 insertions(+), 26 deletions(-) diff --git a/www/grpc/blockchain.go b/www/grpc/blockchain.go index 64114b3dd..46000781d 100644 --- a/www/grpc/blockchain.go +++ b/www/grpc/blockchain.go @@ -33,6 +33,11 @@ func (s *blockchainServer) GetBlockchainInfo(_ context.Context, cv = append(cv, s.validatorToProto(v)) } + pruningHeight := uint32(0) + if s.state.IsPruned() { + pruningHeight = s.state.PruningHeight() + } + return &pactus.GetBlockchainInfoResponse{ LastBlockHeight: s.state.LastBlockHeight(), LastBlockHash: s.state.LastBlockHash().String(), @@ -41,7 +46,7 @@ func (s *blockchainServer) GetBlockchainInfo(_ context.Context, TotalPower: s.state.TotalPower(), CommitteePower: s.state.CommitteePower(), IsPruned: s.state.IsPruned(), - PruningHeight: int32(s.state.PruningHeight()), + PruningHeight: pruningHeight, CommitteeValidators: cv, }, nil } diff --git a/www/grpc/gen/dart/blockchain.pb.dart b/www/grpc/gen/dart/blockchain.pb.dart index 9eaee1d38..f81ed732e 100644 --- a/www/grpc/gen/dart/blockchain.pb.dart +++ b/www/grpc/gen/dart/blockchain.pb.dart @@ -838,7 +838,7 @@ class GetBlockchainInfoResponse extends $pb.GeneratedMessage { ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'committeePower') ..pc(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'committeeValidators', $pb.PbFieldType.PM, subBuilder: ValidatorInfo.create) ..aOB(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'isPruned') - ..a<$core.int>(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pruningHeight', $pb.PbFieldType.O3) + ..a<$core.int>(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pruningHeight', $pb.PbFieldType.OU3) ..hasRequiredFields = false ; @@ -974,7 +974,7 @@ class GetBlockchainInfoResponse extends $pb.GeneratedMessage { @$pb.TagNumber(9) $core.int get pruningHeight => $_getIZ(8); @$pb.TagNumber(9) - set pruningHeight($core.int v) { $_setSignedInt32(8, v); } + set pruningHeight($core.int v) { $_setUnsignedInt32(8, v); } @$pb.TagNumber(9) $core.bool hasPruningHeight() => $_has(8); @$pb.TagNumber(9) diff --git a/www/grpc/gen/dart/blockchain.pbjson.dart b/www/grpc/gen/dart/blockchain.pbjson.dart index 678857444..47e69936f 100644 --- a/www/grpc/gen/dart/blockchain.pbjson.dart +++ b/www/grpc/gen/dart/blockchain.pbjson.dart @@ -208,12 +208,12 @@ const GetBlockchainInfoResponse$json = const { const {'1': 'committee_power', '3': 6, '4': 1, '5': 3, '10': 'committeePower'}, const {'1': 'committee_validators', '3': 7, '4': 3, '5': 11, '6': '.pactus.ValidatorInfo', '10': 'committeeValidators'}, const {'1': 'is_pruned', '3': 8, '4': 1, '5': 8, '10': 'isPruned'}, - const {'1': 'pruning_height', '3': 9, '4': 1, '5': 5, '10': 'pruningHeight'}, + const {'1': 'pruning_height', '3': 9, '4': 1, '5': 13, '10': 'pruningHeight'}, ], }; /// Descriptor for `GetBlockchainInfoResponse`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List getBlockchainInfoResponseDescriptor = $convert.base64Decode('ChlHZXRCbG9ja2NoYWluSW5mb1Jlc3BvbnNlEioKEWxhc3RfYmxvY2tfaGVpZ2h0GAEgASgNUg9sYXN0QmxvY2tIZWlnaHQSJgoPbGFzdF9ibG9ja19oYXNoGAIgASgJUg1sYXN0QmxvY2tIYXNoEiUKDnRvdGFsX2FjY291bnRzGAMgASgFUg10b3RhbEFjY291bnRzEikKEHRvdGFsX3ZhbGlkYXRvcnMYBCABKAVSD3RvdGFsVmFsaWRhdG9ycxIfCgt0b3RhbF9wb3dlchgFIAEoA1IKdG90YWxQb3dlchInCg9jb21taXR0ZWVfcG93ZXIYBiABKANSDmNvbW1pdHRlZVBvd2VyEkgKFGNvbW1pdHRlZV92YWxpZGF0b3JzGAcgAygLMhUucGFjdHVzLlZhbGlkYXRvckluZm9SE2NvbW1pdHRlZVZhbGlkYXRvcnMSGwoJaXNfcHJ1bmVkGAggASgIUghpc1BydW5lZBIlCg5wcnVuaW5nX2hlaWdodBgJIAEoBVINcHJ1bmluZ0hlaWdodA=='); +final $typed_data.Uint8List getBlockchainInfoResponseDescriptor = $convert.base64Decode('ChlHZXRCbG9ja2NoYWluSW5mb1Jlc3BvbnNlEioKEWxhc3RfYmxvY2tfaGVpZ2h0GAEgASgNUg9sYXN0QmxvY2tIZWlnaHQSJgoPbGFzdF9ibG9ja19oYXNoGAIgASgJUg1sYXN0QmxvY2tIYXNoEiUKDnRvdGFsX2FjY291bnRzGAMgASgFUg10b3RhbEFjY291bnRzEikKEHRvdGFsX3ZhbGlkYXRvcnMYBCABKAVSD3RvdGFsVmFsaWRhdG9ycxIfCgt0b3RhbF9wb3dlchgFIAEoA1IKdG90YWxQb3dlchInCg9jb21taXR0ZWVfcG93ZXIYBiABKANSDmNvbW1pdHRlZVBvd2VyEkgKFGNvbW1pdHRlZV92YWxpZGF0b3JzGAcgAygLMhUucGFjdHVzLlZhbGlkYXRvckluZm9SE2NvbW1pdHRlZVZhbGlkYXRvcnMSGwoJaXNfcHJ1bmVkGAggASgIUghpc1BydW5lZBIlCg5wcnVuaW5nX2hlaWdodBgJIAEoDVINcHJ1bmluZ0hlaWdodA=='); @$core.Deprecated('Use getConsensusInfoRequestDescriptor instead') const GetConsensusInfoRequest$json = const { '1': 'GetConsensusInfoRequest', diff --git a/www/grpc/gen/docs/grpc.md b/www/grpc/gen/docs/grpc.md index 7ae2133a9..517313bb2 100644 --- a/www/grpc/gen/docs/grpc.md +++ b/www/grpc/gen/docs/grpc.md @@ -1393,7 +1393,7 @@ Message has no fields. pruning_height - int32 + uint32 Lowest-height block stored (only present if pruning is enabled) diff --git a/www/grpc/gen/go/blockchain.pb.go b/www/grpc/gen/go/blockchain.pb.go index 311cb71db..f0f0cab36 100644 --- a/www/grpc/gen/go/blockchain.pb.go +++ b/www/grpc/gen/go/blockchain.pb.go @@ -981,7 +981,7 @@ type GetBlockchainInfoResponse struct { // If the blocks are subject to pruning. IsPruned bool `protobuf:"varint,8,opt,name=is_pruned,json=isPruned,proto3" json:"is_pruned,omitempty"` // Lowest-height block stored (only present if pruning is enabled) - PruningHeight int32 `protobuf:"varint,9,opt,name=pruning_height,json=pruningHeight,proto3" json:"pruning_height,omitempty"` + PruningHeight uint32 `protobuf:"varint,9,opt,name=pruning_height,json=pruningHeight,proto3" json:"pruning_height,omitempty"` } func (x *GetBlockchainInfoResponse) Reset() { @@ -1072,7 +1072,7 @@ func (x *GetBlockchainInfoResponse) GetIsPruned() bool { return false } -func (x *GetBlockchainInfoResponse) GetPruningHeight() int32 { +func (x *GetBlockchainInfoResponse) GetPruningHeight() uint32 { if x != nil { return x.PruningHeight } @@ -1928,7 +1928,7 @@ var file_blockchain_proto_rawDesc = []byte{ 0x72, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, + 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4f, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, diff --git a/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java b/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java index f7bb37da6..d9726c357 100644 --- a/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java +++ b/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java @@ -10968,7 +10968,7 @@ pactus.blockchain.BlockchainOuterClass.ValidatorInfoOrBuilder getCommitteeValida * Lowest-height block stored (only present if pruning is enabled) * * - * int32 pruning_height = 9 [json_name = "pruningHeight"]; + * uint32 pruning_height = 9 [json_name = "pruningHeight"]; * @return The pruningHeight. */ int getPruningHeight(); @@ -11222,7 +11222,7 @@ public boolean getIsPruned() { * Lowest-height block stored (only present if pruning is enabled) * * - * int32 pruning_height = 9 [json_name = "pruningHeight"]; + * uint32 pruning_height = 9 [json_name = "pruningHeight"]; * @return The pruningHeight. */ @java.lang.Override @@ -11269,7 +11269,7 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) output.writeBool(8, isPruned_); } if (pruningHeight_ != 0) { - output.writeInt32(9, pruningHeight_); + output.writeUInt32(9, pruningHeight_); } getUnknownFields().writeTo(output); } @@ -11313,7 +11313,7 @@ public int getSerializedSize() { } if (pruningHeight_ != 0) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(9, pruningHeight_); + .computeUInt32Size(9, pruningHeight_); } size += getUnknownFields().getSerializedSize(); memoizedSize = size; @@ -11755,7 +11755,7 @@ public Builder mergeFrom( break; } // case 64 case 72: { - pruningHeight_ = input.readInt32(); + pruningHeight_ = input.readUInt32(); break; } // case 72 @@ -12448,7 +12448,7 @@ public Builder clearIsPruned() { * Lowest-height block stored (only present if pruning is enabled) * * - * int32 pruning_height = 9 [json_name = "pruningHeight"]; + * uint32 pruning_height = 9 [json_name = "pruningHeight"]; * @return The pruningHeight. */ @java.lang.Override @@ -12460,7 +12460,7 @@ public int getPruningHeight() { * Lowest-height block stored (only present if pruning is enabled) * * - * int32 pruning_height = 9 [json_name = "pruningHeight"]; + * uint32 pruning_height = 9 [json_name = "pruningHeight"]; * @param value The pruningHeight to set. * @return This builder for chaining. */ @@ -12475,7 +12475,7 @@ public Builder setPruningHeight(int value) { * Lowest-height block stored (only present if pruning is enabled) * * - * int32 pruning_height = 9 [json_name = "pruningHeight"]; + * uint32 pruning_height = 9 [json_name = "pruningHeight"]; * @return This builder for chaining. */ public Builder clearPruningHeight() { @@ -23450,7 +23450,7 @@ public pactus.blockchain.BlockchainOuterClass.ConsensusInfo getDefaultInstanceFo "er\030\006 \001(\003R\016committeePower\022H\n\024committee_va" + "lidators\030\007 \003(\0132\025.pactus.ValidatorInfoR\023c" + "ommitteeValidators\022\033\n\tis_pruned\030\010 \001(\010R\010i" + - "sPruned\022%\n\016pruning_height\030\t \001(\005R\rpruning" + + "sPruned\022%\n\016pruning_height\030\t \001(\rR\rpruning" + "Height\"\031\n\027GetConsensusInfoRequest\"O\n\030Get" + "ConsensusInfoResponse\0223\n\tinstances\030\001 \003(\013" + "2\025.pactus.ConsensusInfoR\tinstances\"Q\n\027Ge" + diff --git a/www/grpc/gen/js/blockchain_pb.js b/www/grpc/gen/js/blockchain_pb.js index 8bde73a8d..fb753154e 100644 --- a/www/grpc/gen/js/blockchain_pb.js +++ b/www/grpc/gen/js/blockchain_pb.js @@ -3110,7 +3110,7 @@ proto.pactus.GetBlockchainInfoResponse.deserializeBinaryFromReader = function(ms msg.setIsPruned(value); break; case 9: - var value = /** @type {number} */ (reader.readInt32()); + var value = /** @type {number} */ (reader.readUint32()); msg.setPruningHeight(value); break; default: @@ -3201,7 +3201,7 @@ proto.pactus.GetBlockchainInfoResponse.serializeBinaryToWriter = function(messag } f = message.getPruningHeight(); if (f !== 0) { - writer.writeInt32( + writer.writeUint32( 9, f ); @@ -3374,7 +3374,7 @@ proto.pactus.GetBlockchainInfoResponse.prototype.setIsPruned = function(value) { /** - * optional int32 pruning_height = 9; + * optional uint32 pruning_height = 9; * @return {number} */ proto.pactus.GetBlockchainInfoResponse.prototype.getPruningHeight = function() { diff --git a/www/grpc/gen/python/blockchain_pb2.py b/www/grpc/gen/python/blockchain_pb2.py index 291bc02cd..6a7e1ae59 100644 --- a/www/grpc/gen/python/blockchain_pb2.py +++ b/www/grpc/gen/python/blockchain_pb2.py @@ -14,7 +14,7 @@ import transaction_pb2 as transaction__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x62lockchain.proto\x12\x06pactus\x1a\x11transaction.proto\"-\n\x11GetAccountRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"C\n\x12GetAccountResponse\x12-\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x13.pactus.AccountInfoR\x07\x61\x63\x63ount\"\x1e\n\x1cGetValidatorAddressesRequest\"=\n\x1dGetValidatorAddressesResponse\x12\x1c\n\taddresses\x18\x01 \x03(\tR\taddresses\"/\n\x13GetValidatorRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x1bGetValidatorByNumberRequest\x12\x16\n\x06number\x18\x01 \x01(\x05R\x06number\"K\n\x14GetValidatorResponse\x12\x33\n\tvalidator\x18\x01 \x01(\x0b\x32\x15.pactus.ValidatorInfoR\tvalidator\"/\n\x13GetPublicKeyRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x14GetPublicKeyResponse\x12\x1d\n\npublic_key\x18\x01 \x01(\tR\tpublicKey\"_\n\x0fGetBlockRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x34\n\tverbosity\x18\x02 \x01(\x0e\x32\x16.pactus.BlockVerbosityR\tverbosity\"\x83\x02\n\x10GetBlockResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x12\n\x04hash\x18\x02 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x03 \x01(\tR\x04\x64\x61ta\x12\x1d\n\nblock_time\x18\x04 \x01(\rR\tblockTime\x12/\n\x06header\x18\x05 \x01(\x0b\x32\x17.pactus.BlockHeaderInfoR\x06header\x12\x34\n\tprev_cert\x18\x06 \x01(\x0b\x32\x17.pactus.CertificateInfoR\x08prevCert\x12)\n\x03txs\x18\x07 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"-\n\x13GetBlockHashRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"*\n\x14GetBlockHashResponse\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"+\n\x15GetBlockHeightRequest\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"0\n\x16GetBlockHeightResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"\x1a\n\x18GetBlockchainInfoRequest\"\x99\x03\n\x19GetBlockchainInfoResponse\x12*\n\x11last_block_height\x18\x01 \x01(\rR\x0flastBlockHeight\x12&\n\x0flast_block_hash\x18\x02 \x01(\tR\rlastBlockHash\x12%\n\x0etotal_accounts\x18\x03 \x01(\x05R\rtotalAccounts\x12)\n\x10total_validators\x18\x04 \x01(\x05R\x0ftotalValidators\x12\x1f\n\x0btotal_power\x18\x05 \x01(\x03R\ntotalPower\x12\'\n\x0f\x63ommittee_power\x18\x06 \x01(\x03R\x0e\x63ommitteePower\x12H\n\x14\x63ommittee_validators\x18\x07 \x03(\x0b\x32\x15.pactus.ValidatorInfoR\x13\x63ommitteeValidators\x12\x1b\n\tis_pruned\x18\x08 \x01(\x08R\x08isPruned\x12%\n\x0epruning_height\x18\t \x01(\x05R\rpruningHeight\"\x19\n\x17GetConsensusInfoRequest\"O\n\x18GetConsensusInfoResponse\x12\x33\n\tinstances\x18\x01 \x03(\x0b\x32\x15.pactus.ConsensusInfoR\tinstances\"Q\n\x17GetTxPoolContentRequest\x12\x36\n\x0cpayload_type\x18\x01 \x01(\x0e\x32\x13.pactus.PayloadTypeR\x0bpayloadType\"E\n\x18GetTxPoolContentResponse\x12)\n\x03txs\x18\x01 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"\xdc\x02\n\rValidatorInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x1d\n\npublic_key\x18\x03 \x01(\tR\tpublicKey\x12\x16\n\x06number\x18\x04 \x01(\x05R\x06number\x12\x14\n\x05stake\x18\x05 \x01(\x03R\x05stake\x12.\n\x13last_bonding_height\x18\x06 \x01(\rR\x11lastBondingHeight\x12\x32\n\x15last_sortition_height\x18\x07 \x01(\rR\x13lastSortitionHeight\x12)\n\x10unbonding_height\x18\x08 \x01(\rR\x0funbondingHeight\x12\x18\n\x07\x61\x64\x64ress\x18\t \x01(\tR\x07\x61\x64\x64ress\x12-\n\x12\x61vailability_score\x18\n \x01(\x01R\x11\x61vailabilityScore\"\x81\x01\n\x0b\x41\x63\x63ountInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x18\n\x07\x62\x61lance\x18\x04 \x01(\x03R\x07\x62\x61lance\x12\x18\n\x07\x61\x64\x64ress\x18\x05 \x01(\tR\x07\x61\x64\x64ress\"\xc4\x01\n\x0f\x42lockHeaderInfo\x12\x18\n\x07version\x18\x01 \x01(\x05R\x07version\x12&\n\x0fprev_block_hash\x18\x02 \x01(\tR\rprevBlockHash\x12\x1d\n\nstate_root\x18\x03 \x01(\tR\tstateRoot\x12%\n\x0esortition_seed\x18\x04 \x01(\tR\rsortitionSeed\x12)\n\x10proposer_address\x18\x05 \x01(\tR\x0fproposerAddress\"\x97\x01\n\x0f\x43\x65rtificateInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x14\n\x05round\x18\x02 \x01(\x05R\x05round\x12\x1e\n\ncommitters\x18\x03 \x03(\x05R\ncommitters\x12\x1c\n\tabsentees\x18\x04 \x03(\x05R\tabsentees\x12\x1c\n\tsignature\x18\x05 \x01(\tR\tsignature\"\xb1\x01\n\x08VoteInfo\x12$\n\x04type\x18\x01 \x01(\x0e\x32\x10.pactus.VoteTypeR\x04type\x12\x14\n\x05voter\x18\x02 \x01(\tR\x05voter\x12\x1d\n\nblock_hash\x18\x03 \x01(\tR\tblockHash\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12\x19\n\x08\x63p_round\x18\x05 \x01(\x05R\x07\x63pRound\x12\x19\n\x08\x63p_value\x18\x06 \x01(\x05R\x07\x63pValue\"\x97\x01\n\rConsensusInfo\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\x12\x16\n\x06\x61\x63tive\x18\x02 \x01(\x08R\x06\x61\x63tive\x12\x16\n\x06height\x18\x03 \x01(\rR\x06height\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12&\n\x05votes\x18\x05 \x03(\x0b\x32\x10.pactus.VoteInfoR\x05votes*H\n\x0e\x42lockVerbosity\x12\x0e\n\nBLOCK_DATA\x10\x00\x12\x0e\n\nBLOCK_INFO\x10\x01\x12\x16\n\x12\x42LOCK_TRANSACTIONS\x10\x02*\\\n\x08VoteType\x12\x10\n\x0cVOTE_UNKNOWN\x10\x00\x12\x10\n\x0cVOTE_PREPARE\x10\x01\x12\x12\n\x0eVOTE_PRECOMMIT\x10\x02\x12\x18\n\x14VOTE_CHANGE_PROPOSER\x10\x03\x32\x8b\x07\n\nBlockchain\x12=\n\x08GetBlock\x12\x17.pactus.GetBlockRequest\x1a\x18.pactus.GetBlockResponse\x12I\n\x0cGetBlockHash\x12\x1b.pactus.GetBlockHashRequest\x1a\x1c.pactus.GetBlockHashResponse\x12O\n\x0eGetBlockHeight\x12\x1d.pactus.GetBlockHeightRequest\x1a\x1e.pactus.GetBlockHeightResponse\x12X\n\x11GetBlockchainInfo\x12 .pactus.GetBlockchainInfoRequest\x1a!.pactus.GetBlockchainInfoResponse\x12U\n\x10GetConsensusInfo\x12\x1f.pactus.GetConsensusInfoRequest\x1a .pactus.GetConsensusInfoResponse\x12\x43\n\nGetAccount\x12\x19.pactus.GetAccountRequest\x1a\x1a.pactus.GetAccountResponse\x12I\n\x0cGetValidator\x12\x1b.pactus.GetValidatorRequest\x1a\x1c.pactus.GetValidatorResponse\x12Y\n\x14GetValidatorByNumber\x12#.pactus.GetValidatorByNumberRequest\x1a\x1c.pactus.GetValidatorResponse\x12\x64\n\x15GetValidatorAddresses\x12$.pactus.GetValidatorAddressesRequest\x1a%.pactus.GetValidatorAddressesResponse\x12I\n\x0cGetPublicKey\x12\x1b.pactus.GetPublicKeyRequest\x1a\x1c.pactus.GetPublicKeyResponse\x12U\n\x10GetTxPoolContent\x12\x1f.pactus.GetTxPoolContentRequest\x1a .pactus.GetTxPoolContentResponseBE\n\x11pactus.blockchainZ0github.com/pactus-project/pactus/www/grpc/pactusb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x62lockchain.proto\x12\x06pactus\x1a\x11transaction.proto\"-\n\x11GetAccountRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"C\n\x12GetAccountResponse\x12-\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x13.pactus.AccountInfoR\x07\x61\x63\x63ount\"\x1e\n\x1cGetValidatorAddressesRequest\"=\n\x1dGetValidatorAddressesResponse\x12\x1c\n\taddresses\x18\x01 \x03(\tR\taddresses\"/\n\x13GetValidatorRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x1bGetValidatorByNumberRequest\x12\x16\n\x06number\x18\x01 \x01(\x05R\x06number\"K\n\x14GetValidatorResponse\x12\x33\n\tvalidator\x18\x01 \x01(\x0b\x32\x15.pactus.ValidatorInfoR\tvalidator\"/\n\x13GetPublicKeyRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x14GetPublicKeyResponse\x12\x1d\n\npublic_key\x18\x01 \x01(\tR\tpublicKey\"_\n\x0fGetBlockRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x34\n\tverbosity\x18\x02 \x01(\x0e\x32\x16.pactus.BlockVerbosityR\tverbosity\"\x83\x02\n\x10GetBlockResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x12\n\x04hash\x18\x02 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x03 \x01(\tR\x04\x64\x61ta\x12\x1d\n\nblock_time\x18\x04 \x01(\rR\tblockTime\x12/\n\x06header\x18\x05 \x01(\x0b\x32\x17.pactus.BlockHeaderInfoR\x06header\x12\x34\n\tprev_cert\x18\x06 \x01(\x0b\x32\x17.pactus.CertificateInfoR\x08prevCert\x12)\n\x03txs\x18\x07 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"-\n\x13GetBlockHashRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"*\n\x14GetBlockHashResponse\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"+\n\x15GetBlockHeightRequest\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"0\n\x16GetBlockHeightResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"\x1a\n\x18GetBlockchainInfoRequest\"\x99\x03\n\x19GetBlockchainInfoResponse\x12*\n\x11last_block_height\x18\x01 \x01(\rR\x0flastBlockHeight\x12&\n\x0flast_block_hash\x18\x02 \x01(\tR\rlastBlockHash\x12%\n\x0etotal_accounts\x18\x03 \x01(\x05R\rtotalAccounts\x12)\n\x10total_validators\x18\x04 \x01(\x05R\x0ftotalValidators\x12\x1f\n\x0btotal_power\x18\x05 \x01(\x03R\ntotalPower\x12\'\n\x0f\x63ommittee_power\x18\x06 \x01(\x03R\x0e\x63ommitteePower\x12H\n\x14\x63ommittee_validators\x18\x07 \x03(\x0b\x32\x15.pactus.ValidatorInfoR\x13\x63ommitteeValidators\x12\x1b\n\tis_pruned\x18\x08 \x01(\x08R\x08isPruned\x12%\n\x0epruning_height\x18\t \x01(\rR\rpruningHeight\"\x19\n\x17GetConsensusInfoRequest\"O\n\x18GetConsensusInfoResponse\x12\x33\n\tinstances\x18\x01 \x03(\x0b\x32\x15.pactus.ConsensusInfoR\tinstances\"Q\n\x17GetTxPoolContentRequest\x12\x36\n\x0cpayload_type\x18\x01 \x01(\x0e\x32\x13.pactus.PayloadTypeR\x0bpayloadType\"E\n\x18GetTxPoolContentResponse\x12)\n\x03txs\x18\x01 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"\xdc\x02\n\rValidatorInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x1d\n\npublic_key\x18\x03 \x01(\tR\tpublicKey\x12\x16\n\x06number\x18\x04 \x01(\x05R\x06number\x12\x14\n\x05stake\x18\x05 \x01(\x03R\x05stake\x12.\n\x13last_bonding_height\x18\x06 \x01(\rR\x11lastBondingHeight\x12\x32\n\x15last_sortition_height\x18\x07 \x01(\rR\x13lastSortitionHeight\x12)\n\x10unbonding_height\x18\x08 \x01(\rR\x0funbondingHeight\x12\x18\n\x07\x61\x64\x64ress\x18\t \x01(\tR\x07\x61\x64\x64ress\x12-\n\x12\x61vailability_score\x18\n \x01(\x01R\x11\x61vailabilityScore\"\x81\x01\n\x0b\x41\x63\x63ountInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x18\n\x07\x62\x61lance\x18\x04 \x01(\x03R\x07\x62\x61lance\x12\x18\n\x07\x61\x64\x64ress\x18\x05 \x01(\tR\x07\x61\x64\x64ress\"\xc4\x01\n\x0f\x42lockHeaderInfo\x12\x18\n\x07version\x18\x01 \x01(\x05R\x07version\x12&\n\x0fprev_block_hash\x18\x02 \x01(\tR\rprevBlockHash\x12\x1d\n\nstate_root\x18\x03 \x01(\tR\tstateRoot\x12%\n\x0esortition_seed\x18\x04 \x01(\tR\rsortitionSeed\x12)\n\x10proposer_address\x18\x05 \x01(\tR\x0fproposerAddress\"\x97\x01\n\x0f\x43\x65rtificateInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x14\n\x05round\x18\x02 \x01(\x05R\x05round\x12\x1e\n\ncommitters\x18\x03 \x03(\x05R\ncommitters\x12\x1c\n\tabsentees\x18\x04 \x03(\x05R\tabsentees\x12\x1c\n\tsignature\x18\x05 \x01(\tR\tsignature\"\xb1\x01\n\x08VoteInfo\x12$\n\x04type\x18\x01 \x01(\x0e\x32\x10.pactus.VoteTypeR\x04type\x12\x14\n\x05voter\x18\x02 \x01(\tR\x05voter\x12\x1d\n\nblock_hash\x18\x03 \x01(\tR\tblockHash\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12\x19\n\x08\x63p_round\x18\x05 \x01(\x05R\x07\x63pRound\x12\x19\n\x08\x63p_value\x18\x06 \x01(\x05R\x07\x63pValue\"\x97\x01\n\rConsensusInfo\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\x12\x16\n\x06\x61\x63tive\x18\x02 \x01(\x08R\x06\x61\x63tive\x12\x16\n\x06height\x18\x03 \x01(\rR\x06height\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12&\n\x05votes\x18\x05 \x03(\x0b\x32\x10.pactus.VoteInfoR\x05votes*H\n\x0e\x42lockVerbosity\x12\x0e\n\nBLOCK_DATA\x10\x00\x12\x0e\n\nBLOCK_INFO\x10\x01\x12\x16\n\x12\x42LOCK_TRANSACTIONS\x10\x02*\\\n\x08VoteType\x12\x10\n\x0cVOTE_UNKNOWN\x10\x00\x12\x10\n\x0cVOTE_PREPARE\x10\x01\x12\x12\n\x0eVOTE_PRECOMMIT\x10\x02\x12\x18\n\x14VOTE_CHANGE_PROPOSER\x10\x03\x32\x8b\x07\n\nBlockchain\x12=\n\x08GetBlock\x12\x17.pactus.GetBlockRequest\x1a\x18.pactus.GetBlockResponse\x12I\n\x0cGetBlockHash\x12\x1b.pactus.GetBlockHashRequest\x1a\x1c.pactus.GetBlockHashResponse\x12O\n\x0eGetBlockHeight\x12\x1d.pactus.GetBlockHeightRequest\x1a\x1e.pactus.GetBlockHeightResponse\x12X\n\x11GetBlockchainInfo\x12 .pactus.GetBlockchainInfoRequest\x1a!.pactus.GetBlockchainInfoResponse\x12U\n\x10GetConsensusInfo\x12\x1f.pactus.GetConsensusInfoRequest\x1a .pactus.GetConsensusInfoResponse\x12\x43\n\nGetAccount\x12\x19.pactus.GetAccountRequest\x1a\x1a.pactus.GetAccountResponse\x12I\n\x0cGetValidator\x12\x1b.pactus.GetValidatorRequest\x1a\x1c.pactus.GetValidatorResponse\x12Y\n\x14GetValidatorByNumber\x12#.pactus.GetValidatorByNumberRequest\x1a\x1c.pactus.GetValidatorResponse\x12\x64\n\x15GetValidatorAddresses\x12$.pactus.GetValidatorAddressesRequest\x1a%.pactus.GetValidatorAddressesResponse\x12I\n\x0cGetPublicKey\x12\x1b.pactus.GetPublicKeyRequest\x1a\x1c.pactus.GetPublicKeyResponse\x12U\n\x10GetTxPoolContent\x12\x1f.pactus.GetTxPoolContentRequest\x1a .pactus.GetTxPoolContentResponseBE\n\x11pactus.blockchainZ0github.com/pactus-project/pactus/www/grpc/pactusb\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'blockchain_pb2', globals()) diff --git a/www/grpc/gen/rust/pactus.rs b/www/grpc/gen/rust/pactus.rs index f0eba66d4..f6523c3c8 100644 --- a/www/grpc/gen/rust/pactus.rs +++ b/www/grpc/gen/rust/pactus.rs @@ -530,8 +530,8 @@ pub struct GetBlockchainInfoResponse { #[prost(bool, tag="8")] pub is_pruned: bool, /// Lowest-height block stored (only present if pruning is enabled) - #[prost(int32, tag="9")] - pub pruning_height: i32, + #[prost(uint32, tag="9")] + pub pruning_height: u32, } /// Message to request consensus information. #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/www/grpc/proto/blockchain.proto b/www/grpc/proto/blockchain.proto index 38fd8c0cc..b79d670b9 100644 --- a/www/grpc/proto/blockchain.proto +++ b/www/grpc/proto/blockchain.proto @@ -176,7 +176,7 @@ message GetBlockchainInfoResponse { // If the blocks are subject to pruning. bool is_pruned = 8; // Lowest-height block stored (only present if pruning is enabled) - int32 pruning_height = 9; + uint32 pruning_height = 9; } // Message to request consensus information. diff --git a/www/grpc/swagger-ui/pactus.swagger.json b/www/grpc/swagger-ui/pactus.swagger.json index c20f91dab..7fd39a564 100644 --- a/www/grpc/swagger-ui/pactus.swagger.json +++ b/www/grpc/swagger-ui/pactus.swagger.json @@ -1622,7 +1622,7 @@ }, "pruningHeight": { "type": "integer", - "format": "int32", + "format": "int64", "title": "Lowest-height block stored (only present if pruning is enabled)" } }, From 3a9a3be984b7a352411023e3b6c37f8129d31235 Mon Sep 17 00:00:00 2001 From: Javad Rajabzadeh Date: Sun, 21 Jul 2024 18:24:32 +0330 Subject: [PATCH 3/6] fix(grpc): add last-block-time to blockchain-info API (#1428) --- www/grpc/blockchain.go | 1 + www/grpc/gen/dart/blockchain.pb.dart | 14 + www/grpc/gen/dart/blockchain.pbjson.dart | 3 +- www/grpc/gen/docs/grpc.md | 7 + www/grpc/gen/docs/json-rpc.md | 7 + www/grpc/gen/go/blockchain.pb.go | 333 +++++++++--------- .../blockchain/BlockchainOuterClass.java | 98 +++++- www/grpc/gen/js/blockchain_pb.js | 32 +- www/grpc/gen/python/blockchain_pb2.py | 56 +-- www/grpc/gen/rust/pactus.rs | 3 + www/grpc/gen/rust/pactus.serde.rs | 20 ++ www/grpc/proto/blockchain.proto | 2 + www/grpc/swagger-ui/pactus.swagger.json | 5 + 13 files changed, 387 insertions(+), 194 deletions(-) diff --git a/www/grpc/blockchain.go b/www/grpc/blockchain.go index 46000781d..de1b62ddb 100644 --- a/www/grpc/blockchain.go +++ b/www/grpc/blockchain.go @@ -47,6 +47,7 @@ func (s *blockchainServer) GetBlockchainInfo(_ context.Context, CommitteePower: s.state.CommitteePower(), IsPruned: s.state.IsPruned(), PruningHeight: pruningHeight, + LastBlockTime: s.state.LastBlockTime().Unix(), CommitteeValidators: cv, }, nil } diff --git a/www/grpc/gen/dart/blockchain.pb.dart b/www/grpc/gen/dart/blockchain.pb.dart index f81ed732e..34e41f776 100644 --- a/www/grpc/gen/dart/blockchain.pb.dart +++ b/www/grpc/gen/dart/blockchain.pb.dart @@ -839,6 +839,7 @@ class GetBlockchainInfoResponse extends $pb.GeneratedMessage { ..pc(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'committeeValidators', $pb.PbFieldType.PM, subBuilder: ValidatorInfo.create) ..aOB(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'isPruned') ..a<$core.int>(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pruningHeight', $pb.PbFieldType.OU3) + ..aInt64(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'lastBlockTime') ..hasRequiredFields = false ; @@ -853,6 +854,7 @@ class GetBlockchainInfoResponse extends $pb.GeneratedMessage { $core.Iterable? committeeValidators, $core.bool? isPruned, $core.int? pruningHeight, + $fixnum.Int64? lastBlockTime, }) { final _result = create(); if (lastBlockHeight != null) { @@ -882,6 +884,9 @@ class GetBlockchainInfoResponse extends $pb.GeneratedMessage { if (pruningHeight != null) { _result.pruningHeight = pruningHeight; } + if (lastBlockTime != null) { + _result.lastBlockTime = lastBlockTime; + } return _result; } factory GetBlockchainInfoResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); @@ -979,6 +984,15 @@ class GetBlockchainInfoResponse extends $pb.GeneratedMessage { $core.bool hasPruningHeight() => $_has(8); @$pb.TagNumber(9) void clearPruningHeight() => clearField(9); + + @$pb.TagNumber(10) + $fixnum.Int64 get lastBlockTime => $_getI64(9); + @$pb.TagNumber(10) + set lastBlockTime($fixnum.Int64 v) { $_setInt64(9, v); } + @$pb.TagNumber(10) + $core.bool hasLastBlockTime() => $_has(9); + @$pb.TagNumber(10) + void clearLastBlockTime() => clearField(10); } class GetConsensusInfoRequest extends $pb.GeneratedMessage { diff --git a/www/grpc/gen/dart/blockchain.pbjson.dart b/www/grpc/gen/dart/blockchain.pbjson.dart index 47e69936f..ee97ec9d3 100644 --- a/www/grpc/gen/dart/blockchain.pbjson.dart +++ b/www/grpc/gen/dart/blockchain.pbjson.dart @@ -209,11 +209,12 @@ const GetBlockchainInfoResponse$json = const { const {'1': 'committee_validators', '3': 7, '4': 3, '5': 11, '6': '.pactus.ValidatorInfo', '10': 'committeeValidators'}, const {'1': 'is_pruned', '3': 8, '4': 1, '5': 8, '10': 'isPruned'}, const {'1': 'pruning_height', '3': 9, '4': 1, '5': 13, '10': 'pruningHeight'}, + const {'1': 'last_block_time', '3': 10, '4': 1, '5': 3, '10': 'lastBlockTime'}, ], }; /// Descriptor for `GetBlockchainInfoResponse`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List getBlockchainInfoResponseDescriptor = $convert.base64Decode('ChlHZXRCbG9ja2NoYWluSW5mb1Jlc3BvbnNlEioKEWxhc3RfYmxvY2tfaGVpZ2h0GAEgASgNUg9sYXN0QmxvY2tIZWlnaHQSJgoPbGFzdF9ibG9ja19oYXNoGAIgASgJUg1sYXN0QmxvY2tIYXNoEiUKDnRvdGFsX2FjY291bnRzGAMgASgFUg10b3RhbEFjY291bnRzEikKEHRvdGFsX3ZhbGlkYXRvcnMYBCABKAVSD3RvdGFsVmFsaWRhdG9ycxIfCgt0b3RhbF9wb3dlchgFIAEoA1IKdG90YWxQb3dlchInCg9jb21taXR0ZWVfcG93ZXIYBiABKANSDmNvbW1pdHRlZVBvd2VyEkgKFGNvbW1pdHRlZV92YWxpZGF0b3JzGAcgAygLMhUucGFjdHVzLlZhbGlkYXRvckluZm9SE2NvbW1pdHRlZVZhbGlkYXRvcnMSGwoJaXNfcHJ1bmVkGAggASgIUghpc1BydW5lZBIlCg5wcnVuaW5nX2hlaWdodBgJIAEoDVINcHJ1bmluZ0hlaWdodA=='); +final $typed_data.Uint8List getBlockchainInfoResponseDescriptor = $convert.base64Decode('ChlHZXRCbG9ja2NoYWluSW5mb1Jlc3BvbnNlEioKEWxhc3RfYmxvY2tfaGVpZ2h0GAEgASgNUg9sYXN0QmxvY2tIZWlnaHQSJgoPbGFzdF9ibG9ja19oYXNoGAIgASgJUg1sYXN0QmxvY2tIYXNoEiUKDnRvdGFsX2FjY291bnRzGAMgASgFUg10b3RhbEFjY291bnRzEikKEHRvdGFsX3ZhbGlkYXRvcnMYBCABKAVSD3RvdGFsVmFsaWRhdG9ycxIfCgt0b3RhbF9wb3dlchgFIAEoA1IKdG90YWxQb3dlchInCg9jb21taXR0ZWVfcG93ZXIYBiABKANSDmNvbW1pdHRlZVBvd2VyEkgKFGNvbW1pdHRlZV92YWxpZGF0b3JzGAcgAygLMhUucGFjdHVzLlZhbGlkYXRvckluZm9SE2NvbW1pdHRlZVZhbGlkYXRvcnMSGwoJaXNfcHJ1bmVkGAggASgIUghpc1BydW5lZBIlCg5wcnVuaW5nX2hlaWdodBgJIAEoDVINcHJ1bmluZ0hlaWdodBImCg9sYXN0X2Jsb2NrX3RpbWUYCiABKANSDWxhc3RCbG9ja1RpbWU='); @$core.Deprecated('Use getConsensusInfoRequestDescriptor instead') const GetConsensusInfoRequest$json = const { '1': 'GetConsensusInfoRequest', diff --git a/www/grpc/gen/docs/grpc.md b/www/grpc/gen/docs/grpc.md index 517313bb2..f0250feec 100644 --- a/www/grpc/gen/docs/grpc.md +++ b/www/grpc/gen/docs/grpc.md @@ -1397,6 +1397,13 @@ Message has no fields. Lowest-height block stored (only present if pruning is enabled) + + + last_block_time + int64 + + The last block time as timestamp + diff --git a/www/grpc/gen/docs/json-rpc.md b/www/grpc/gen/docs/json-rpc.md index f3f877b5d..dd4ce6fec 100644 --- a/www/grpc/gen/docs/json-rpc.md +++ b/www/grpc/gen/docs/json-rpc.md @@ -1398,6 +1398,13 @@ Parameters has no fields. Lowest-height block stored (only present if pruning is enabled) + + + last_block_time + numeric + + The last block time as timestamp + diff --git a/www/grpc/gen/go/blockchain.pb.go b/www/grpc/gen/go/blockchain.pb.go index f0f0cab36..a13c249ca 100644 --- a/www/grpc/gen/go/blockchain.pb.go +++ b/www/grpc/gen/go/blockchain.pb.go @@ -982,6 +982,8 @@ type GetBlockchainInfoResponse struct { IsPruned bool `protobuf:"varint,8,opt,name=is_pruned,json=isPruned,proto3" json:"is_pruned,omitempty"` // Lowest-height block stored (only present if pruning is enabled) PruningHeight uint32 `protobuf:"varint,9,opt,name=pruning_height,json=pruningHeight,proto3" json:"pruning_height,omitempty"` + // The last block time as timestamp + LastBlockTime int64 `protobuf:"varint,10,opt,name=last_block_time,json=lastBlockTime,proto3" json:"last_block_time,omitempty"` } func (x *GetBlockchainInfoResponse) Reset() { @@ -1079,6 +1081,13 @@ func (x *GetBlockchainInfoResponse) GetPruningHeight() uint32 { return 0 } +func (x *GetBlockchainInfoResponse) GetLastBlockTime() int64 { + if x != nil { + return x.LastBlockTime + } + return 0 +} + // Message to request consensus information. type GetConsensusInfoRequest struct { state protoimpl.MessageState @@ -1903,7 +1912,7 @@ var file_blockchain_proto_rawDesc = []byte{ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x99, 0x03, 0x0a, 0x19, 0x47, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xc1, 0x03, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, @@ -1929,169 +1938,171 @@ var file_blockchain_proto_rawDesc = []byte{ 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x70, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, - 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, - 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x4f, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, - 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, - 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, - 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x73, 0x22, 0x51, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, - 0x0c, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x50, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x54, 0x79, 0x70, 0x65, 0x22, 0x45, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, - 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x29, 0x0a, 0x03, 0x74, 0x78, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x03, 0x74, 0x78, 0x73, 0x22, 0xdc, 0x02, 0x0a, - 0x0d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x6b, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x42, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x72, 0x74, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x62, 0x6f, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0f, 0x75, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2d, 0x0a, 0x12, - 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x63, 0x6f, - 0x72, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x0b, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, - 0xc4, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, - 0x0f, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, - 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6f, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, - 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x70, - 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x0f, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, - 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, - 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, - 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x62, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x09, 0x61, 0x62, 0x73, 0x65, 0x6e, 0x74, 0x65, - 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x22, 0xb1, 0x01, 0x0a, 0x08, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x24, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x70, 0x61, - 0x63, 0x74, 0x75, 0x73, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x63, 0x70, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x07, 0x63, 0x70, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x70, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, - 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x19, + 0x0a, 0x17, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4f, 0x0a, 0x18, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, + 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x51, 0x0a, 0x17, 0x47, 0x65, + 0x74, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x70, 0x61, + 0x63, 0x74, 0x75, 0x73, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x22, 0x45, 0x0a, + 0x18, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x03, 0x74, 0x78, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x03, 0x74, 0x78, 0x73, 0x22, 0xdc, 0x02, 0x0a, 0x0d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, + 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x42, 0x6f, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6c, 0x61, 0x73, 0x74, + 0x53, 0x6f, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x75, 0x6e, 0x62, 0x6f, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x11, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, + 0x6f, 0x72, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x70, 0x72, 0x65, 0x76, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x73, 0x6f, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, + 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x97, + 0x01, 0x0a, 0x0f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1e, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, + 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x09, + 0x61, 0x62, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, + 0x09, 0x61, 0x62, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xb1, 0x01, 0x0a, 0x08, 0x56, 0x6f, 0x74, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x24, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x56, 0x6f, 0x74, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x6f, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, + 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x56, - 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x2a, 0x48, - 0x0a, 0x0e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x69, 0x74, 0x79, - 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, - 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x01, - 0x12, 0x16, 0x0a, 0x12, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x41, - 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x02, 0x2a, 0x5c, 0x0a, 0x08, 0x56, 0x6f, 0x74, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x50, - 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4f, 0x54, 0x45, - 0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, - 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x50, - 0x4f, 0x53, 0x45, 0x52, 0x10, 0x03, 0x32, 0x8b, 0x07, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x12, 0x17, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x61, 0x63, - 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x58, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, - 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, - 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, - 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, - 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x43, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x19, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x61, 0x63, - 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, - 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, - 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x59, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x70, 0x61, 0x63, 0x74, - 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, - 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x5f, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x70, 0x52, 0x6f, 0x75, 0x6e, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x97, 0x01, 0x0a, + 0x0d, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x26, + 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x2a, 0x48, 0x0a, 0x0e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, + 0x65, 0x72, 0x62, 0x6f, 0x73, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x4c, 0x4f, 0x43, + 0x4b, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x4c, 0x4f, 0x43, + 0x4b, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x42, 0x4c, 0x4f, 0x43, + 0x4b, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x02, + 0x2a, 0x5c, 0x0a, 0x08, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, + 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x10, + 0x0a, 0x0c, 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x10, 0x01, + 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x4d, + 0x49, 0x54, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x48, 0x41, + 0x4e, 0x47, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x50, 0x4f, 0x53, 0x45, 0x52, 0x10, 0x03, 0x32, 0x8b, + 0x07, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x3d, 0x0a, + 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x17, 0x2e, 0x70, 0x61, 0x63, 0x74, + 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, + 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x2e, 0x70, + 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, + 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x61, 0x63, 0x74, + 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, + 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x2e, + 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, + 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, + 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, + 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x15, - 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, - 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x61, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, - 0x65, 0x79, 0x12, 0x1b, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, - 0x10, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x12, 0x1f, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x78, - 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x54, - 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x45, 0x0a, 0x11, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2d, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2f, 0x77, 0x77, 0x77, 0x2f, - 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x14, 0x47, 0x65, 0x74, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x12, 0x23, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, + 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x24, 0x2e, + 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x2e, 0x70, 0x61, 0x63, + 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, + 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, + 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x70, 0x61, 0x63, 0x74, + 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x61, 0x63, + 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x45, 0x0a, 0x11, + 0x70, 0x61, 0x63, 0x74, 0x75, 0x73, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x61, + 0x63, 0x74, 0x75, 0x73, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x70, 0x61, 0x63, + 0x74, 0x75, 0x73, 0x2f, 0x77, 0x77, 0x77, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x61, 0x63, + 0x74, 0x75, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java b/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java index d9726c357..d8e174538 100644 --- a/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java +++ b/www/grpc/gen/java/pactus/blockchain/BlockchainOuterClass.java @@ -10972,6 +10972,16 @@ pactus.blockchain.BlockchainOuterClass.ValidatorInfoOrBuilder getCommitteeValida * @return The pruningHeight. */ int getPruningHeight(); + + /** + *
+     * The last block time as timestamp
+     * 
+ * + * int64 last_block_time = 10 [json_name = "lastBlockTime"]; + * @return The lastBlockTime. + */ + long getLastBlockTime(); } /** *
@@ -11230,6 +11240,21 @@ public int getPruningHeight() {
       return pruningHeight_;
     }
 
+    public static final int LAST_BLOCK_TIME_FIELD_NUMBER = 10;
+    private long lastBlockTime_;
+    /**
+     * 
+     * The last block time as timestamp
+     * 
+ * + * int64 last_block_time = 10 [json_name = "lastBlockTime"]; + * @return The lastBlockTime. + */ + @java.lang.Override + public long getLastBlockTime() { + return lastBlockTime_; + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -11271,6 +11296,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (pruningHeight_ != 0) { output.writeUInt32(9, pruningHeight_); } + if (lastBlockTime_ != 0L) { + output.writeInt64(10, lastBlockTime_); + } getUnknownFields().writeTo(output); } @@ -11315,6 +11343,10 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(9, pruningHeight_); } + if (lastBlockTime_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(10, lastBlockTime_); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -11348,6 +11380,8 @@ public boolean equals(final java.lang.Object obj) { != other.getIsPruned()) return false; if (getPruningHeight() != other.getPruningHeight()) return false; + if (getLastBlockTime() + != other.getLastBlockTime()) return false; if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; } @@ -11382,6 +11416,9 @@ public int hashCode() { getIsPruned()); hash = (37 * hash) + PRUNING_HEIGHT_FIELD_NUMBER; hash = (53 * hash) + getPruningHeight(); + hash = (37 * hash) + LAST_BLOCK_TIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getLastBlockTime()); hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -11537,6 +11574,8 @@ public Builder clear() { pruningHeight_ = 0; + lastBlockTime_ = 0L; + return this; } @@ -11581,6 +11620,7 @@ public pactus.blockchain.BlockchainOuterClass.GetBlockchainInfoResponse buildPar } result.isPruned_ = isPruned_; result.pruningHeight_ = pruningHeight_; + result.lastBlockTime_ = lastBlockTime_; onBuilt(); return result; } @@ -11680,6 +11720,9 @@ public Builder mergeFrom(pactus.blockchain.BlockchainOuterClass.GetBlockchainInf if (other.getPruningHeight() != 0) { setPruningHeight(other.getPruningHeight()); } + if (other.getLastBlockTime() != 0L) { + setLastBlockTime(other.getLastBlockTime()); + } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; @@ -11759,6 +11802,11 @@ public Builder mergeFrom( break; } // case 72 + case 80: { + lastBlockTime_ = input.readInt64(); + + break; + } // case 80 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -12484,6 +12532,49 @@ public Builder clearPruningHeight() { onChanged(); return this; } + + private long lastBlockTime_ ; + /** + *
+       * The last block time as timestamp
+       * 
+ * + * int64 last_block_time = 10 [json_name = "lastBlockTime"]; + * @return The lastBlockTime. + */ + @java.lang.Override + public long getLastBlockTime() { + return lastBlockTime_; + } + /** + *
+       * The last block time as timestamp
+       * 
+ * + * int64 last_block_time = 10 [json_name = "lastBlockTime"]; + * @param value The lastBlockTime to set. + * @return This builder for chaining. + */ + public Builder setLastBlockTime(long value) { + + lastBlockTime_ = value; + onChanged(); + return this; + } + /** + *
+       * The last block time as timestamp
+       * 
+ * + * int64 last_block_time = 10 [json_name = "lastBlockTime"]; + * @return This builder for chaining. + */ + public Builder clearLastBlockTime() { + + lastBlockTime_ = 0L; + onChanged(); + return this; + } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { @@ -23440,7 +23531,7 @@ public pactus.blockchain.BlockchainOuterClass.ConsensusInfo getDefaultInstanceFo "ashResponse\022\022\n\004hash\030\001 \001(\tR\004hash\"+\n\025GetBl" + "ockHeightRequest\022\022\n\004hash\030\001 \001(\tR\004hash\"0\n\026" + "GetBlockHeightResponse\022\026\n\006height\030\001 \001(\rR\006" + - "height\"\032\n\030GetBlockchainInfoRequest\"\231\003\n\031G" + + "height\"\032\n\030GetBlockchainInfoRequest\"\301\003\n\031G" + "etBlockchainInfoResponse\022*\n\021last_block_h" + "eight\030\001 \001(\rR\017lastBlockHeight\022&\n\017last_blo" + "ck_hash\030\002 \001(\tR\rlastBlockHash\022%\n\016total_ac" + @@ -23451,7 +23542,8 @@ public pactus.blockchain.BlockchainOuterClass.ConsensusInfo getDefaultInstanceFo "lidators\030\007 \003(\0132\025.pactus.ValidatorInfoR\023c" + "ommitteeValidators\022\033\n\tis_pruned\030\010 \001(\010R\010i" + "sPruned\022%\n\016pruning_height\030\t \001(\rR\rpruning" + - "Height\"\031\n\027GetConsensusInfoRequest\"O\n\030Get" + + "Height\022&\n\017last_block_time\030\n \001(\003R\rlastBlo" + + "ckTime\"\031\n\027GetConsensusInfoRequest\"O\n\030Get" + "ConsensusInfoResponse\0223\n\tinstances\030\001 \003(\013" + "2\025.pactus.ConsensusInfoR\tinstances\"Q\n\027Ge" + "tTxPoolContentRequest\0226\n\014payload_type\030\001 " + @@ -23623,7 +23715,7 @@ public pactus.blockchain.BlockchainOuterClass.ConsensusInfo getDefaultInstanceFo internal_static_pactus_GetBlockchainInfoResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_pactus_GetBlockchainInfoResponse_descriptor, - new java.lang.String[] { "LastBlockHeight", "LastBlockHash", "TotalAccounts", "TotalValidators", "TotalPower", "CommitteePower", "CommitteeValidators", "IsPruned", "PruningHeight", }); + new java.lang.String[] { "LastBlockHeight", "LastBlockHash", "TotalAccounts", "TotalValidators", "TotalPower", "CommitteePower", "CommitteeValidators", "IsPruned", "PruningHeight", "LastBlockTime", }); internal_static_pactus_GetConsensusInfoRequest_descriptor = getDescriptor().getMessageTypes().get(17); internal_static_pactus_GetConsensusInfoRequest_fieldAccessorTable = new diff --git a/www/grpc/gen/js/blockchain_pb.js b/www/grpc/gen/js/blockchain_pb.js index fb753154e..b763cfa55 100644 --- a/www/grpc/gen/js/blockchain_pb.js +++ b/www/grpc/gen/js/blockchain_pb.js @@ -3039,7 +3039,8 @@ proto.pactus.GetBlockchainInfoResponse.toObject = function(includeInstance, msg) committeeValidatorsList: jspb.Message.toObjectList(msg.getCommitteeValidatorsList(), proto.pactus.ValidatorInfo.toObject, includeInstance), isPruned: jspb.Message.getBooleanFieldWithDefault(msg, 8, false), - pruningHeight: jspb.Message.getFieldWithDefault(msg, 9, 0) + pruningHeight: jspb.Message.getFieldWithDefault(msg, 9, 0), + lastBlockTime: jspb.Message.getFieldWithDefault(msg, 10, 0) }; if (includeInstance) { @@ -3113,6 +3114,10 @@ proto.pactus.GetBlockchainInfoResponse.deserializeBinaryFromReader = function(ms var value = /** @type {number} */ (reader.readUint32()); msg.setPruningHeight(value); break; + case 10: + var value = /** @type {number} */ (reader.readInt64()); + msg.setLastBlockTime(value); + break; default: reader.skipField(); break; @@ -3206,6 +3211,13 @@ proto.pactus.GetBlockchainInfoResponse.serializeBinaryToWriter = function(messag f ); } + f = message.getLastBlockTime(); + if (f !== 0) { + writer.writeInt64( + 10, + f + ); + } }; @@ -3391,6 +3403,24 @@ proto.pactus.GetBlockchainInfoResponse.prototype.setPruningHeight = function(val }; +/** + * optional int64 last_block_time = 10; + * @return {number} + */ +proto.pactus.GetBlockchainInfoResponse.prototype.getLastBlockTime = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 10, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.pactus.GetBlockchainInfoResponse} returns this + */ +proto.pactus.GetBlockchainInfoResponse.prototype.setLastBlockTime = function(value) { + return jspb.Message.setProto3IntField(this, 10, value); +}; + + diff --git a/www/grpc/gen/python/blockchain_pb2.py b/www/grpc/gen/python/blockchain_pb2.py index 6a7e1ae59..4fd752f31 100644 --- a/www/grpc/gen/python/blockchain_pb2.py +++ b/www/grpc/gen/python/blockchain_pb2.py @@ -14,7 +14,7 @@ import transaction_pb2 as transaction__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x62lockchain.proto\x12\x06pactus\x1a\x11transaction.proto\"-\n\x11GetAccountRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"C\n\x12GetAccountResponse\x12-\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x13.pactus.AccountInfoR\x07\x61\x63\x63ount\"\x1e\n\x1cGetValidatorAddressesRequest\"=\n\x1dGetValidatorAddressesResponse\x12\x1c\n\taddresses\x18\x01 \x03(\tR\taddresses\"/\n\x13GetValidatorRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x1bGetValidatorByNumberRequest\x12\x16\n\x06number\x18\x01 \x01(\x05R\x06number\"K\n\x14GetValidatorResponse\x12\x33\n\tvalidator\x18\x01 \x01(\x0b\x32\x15.pactus.ValidatorInfoR\tvalidator\"/\n\x13GetPublicKeyRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x14GetPublicKeyResponse\x12\x1d\n\npublic_key\x18\x01 \x01(\tR\tpublicKey\"_\n\x0fGetBlockRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x34\n\tverbosity\x18\x02 \x01(\x0e\x32\x16.pactus.BlockVerbosityR\tverbosity\"\x83\x02\n\x10GetBlockResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x12\n\x04hash\x18\x02 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x03 \x01(\tR\x04\x64\x61ta\x12\x1d\n\nblock_time\x18\x04 \x01(\rR\tblockTime\x12/\n\x06header\x18\x05 \x01(\x0b\x32\x17.pactus.BlockHeaderInfoR\x06header\x12\x34\n\tprev_cert\x18\x06 \x01(\x0b\x32\x17.pactus.CertificateInfoR\x08prevCert\x12)\n\x03txs\x18\x07 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"-\n\x13GetBlockHashRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"*\n\x14GetBlockHashResponse\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"+\n\x15GetBlockHeightRequest\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"0\n\x16GetBlockHeightResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"\x1a\n\x18GetBlockchainInfoRequest\"\x99\x03\n\x19GetBlockchainInfoResponse\x12*\n\x11last_block_height\x18\x01 \x01(\rR\x0flastBlockHeight\x12&\n\x0flast_block_hash\x18\x02 \x01(\tR\rlastBlockHash\x12%\n\x0etotal_accounts\x18\x03 \x01(\x05R\rtotalAccounts\x12)\n\x10total_validators\x18\x04 \x01(\x05R\x0ftotalValidators\x12\x1f\n\x0btotal_power\x18\x05 \x01(\x03R\ntotalPower\x12\'\n\x0f\x63ommittee_power\x18\x06 \x01(\x03R\x0e\x63ommitteePower\x12H\n\x14\x63ommittee_validators\x18\x07 \x03(\x0b\x32\x15.pactus.ValidatorInfoR\x13\x63ommitteeValidators\x12\x1b\n\tis_pruned\x18\x08 \x01(\x08R\x08isPruned\x12%\n\x0epruning_height\x18\t \x01(\rR\rpruningHeight\"\x19\n\x17GetConsensusInfoRequest\"O\n\x18GetConsensusInfoResponse\x12\x33\n\tinstances\x18\x01 \x03(\x0b\x32\x15.pactus.ConsensusInfoR\tinstances\"Q\n\x17GetTxPoolContentRequest\x12\x36\n\x0cpayload_type\x18\x01 \x01(\x0e\x32\x13.pactus.PayloadTypeR\x0bpayloadType\"E\n\x18GetTxPoolContentResponse\x12)\n\x03txs\x18\x01 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"\xdc\x02\n\rValidatorInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x1d\n\npublic_key\x18\x03 \x01(\tR\tpublicKey\x12\x16\n\x06number\x18\x04 \x01(\x05R\x06number\x12\x14\n\x05stake\x18\x05 \x01(\x03R\x05stake\x12.\n\x13last_bonding_height\x18\x06 \x01(\rR\x11lastBondingHeight\x12\x32\n\x15last_sortition_height\x18\x07 \x01(\rR\x13lastSortitionHeight\x12)\n\x10unbonding_height\x18\x08 \x01(\rR\x0funbondingHeight\x12\x18\n\x07\x61\x64\x64ress\x18\t \x01(\tR\x07\x61\x64\x64ress\x12-\n\x12\x61vailability_score\x18\n \x01(\x01R\x11\x61vailabilityScore\"\x81\x01\n\x0b\x41\x63\x63ountInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x18\n\x07\x62\x61lance\x18\x04 \x01(\x03R\x07\x62\x61lance\x12\x18\n\x07\x61\x64\x64ress\x18\x05 \x01(\tR\x07\x61\x64\x64ress\"\xc4\x01\n\x0f\x42lockHeaderInfo\x12\x18\n\x07version\x18\x01 \x01(\x05R\x07version\x12&\n\x0fprev_block_hash\x18\x02 \x01(\tR\rprevBlockHash\x12\x1d\n\nstate_root\x18\x03 \x01(\tR\tstateRoot\x12%\n\x0esortition_seed\x18\x04 \x01(\tR\rsortitionSeed\x12)\n\x10proposer_address\x18\x05 \x01(\tR\x0fproposerAddress\"\x97\x01\n\x0f\x43\x65rtificateInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x14\n\x05round\x18\x02 \x01(\x05R\x05round\x12\x1e\n\ncommitters\x18\x03 \x03(\x05R\ncommitters\x12\x1c\n\tabsentees\x18\x04 \x03(\x05R\tabsentees\x12\x1c\n\tsignature\x18\x05 \x01(\tR\tsignature\"\xb1\x01\n\x08VoteInfo\x12$\n\x04type\x18\x01 \x01(\x0e\x32\x10.pactus.VoteTypeR\x04type\x12\x14\n\x05voter\x18\x02 \x01(\tR\x05voter\x12\x1d\n\nblock_hash\x18\x03 \x01(\tR\tblockHash\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12\x19\n\x08\x63p_round\x18\x05 \x01(\x05R\x07\x63pRound\x12\x19\n\x08\x63p_value\x18\x06 \x01(\x05R\x07\x63pValue\"\x97\x01\n\rConsensusInfo\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\x12\x16\n\x06\x61\x63tive\x18\x02 \x01(\x08R\x06\x61\x63tive\x12\x16\n\x06height\x18\x03 \x01(\rR\x06height\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12&\n\x05votes\x18\x05 \x03(\x0b\x32\x10.pactus.VoteInfoR\x05votes*H\n\x0e\x42lockVerbosity\x12\x0e\n\nBLOCK_DATA\x10\x00\x12\x0e\n\nBLOCK_INFO\x10\x01\x12\x16\n\x12\x42LOCK_TRANSACTIONS\x10\x02*\\\n\x08VoteType\x12\x10\n\x0cVOTE_UNKNOWN\x10\x00\x12\x10\n\x0cVOTE_PREPARE\x10\x01\x12\x12\n\x0eVOTE_PRECOMMIT\x10\x02\x12\x18\n\x14VOTE_CHANGE_PROPOSER\x10\x03\x32\x8b\x07\n\nBlockchain\x12=\n\x08GetBlock\x12\x17.pactus.GetBlockRequest\x1a\x18.pactus.GetBlockResponse\x12I\n\x0cGetBlockHash\x12\x1b.pactus.GetBlockHashRequest\x1a\x1c.pactus.GetBlockHashResponse\x12O\n\x0eGetBlockHeight\x12\x1d.pactus.GetBlockHeightRequest\x1a\x1e.pactus.GetBlockHeightResponse\x12X\n\x11GetBlockchainInfo\x12 .pactus.GetBlockchainInfoRequest\x1a!.pactus.GetBlockchainInfoResponse\x12U\n\x10GetConsensusInfo\x12\x1f.pactus.GetConsensusInfoRequest\x1a .pactus.GetConsensusInfoResponse\x12\x43\n\nGetAccount\x12\x19.pactus.GetAccountRequest\x1a\x1a.pactus.GetAccountResponse\x12I\n\x0cGetValidator\x12\x1b.pactus.GetValidatorRequest\x1a\x1c.pactus.GetValidatorResponse\x12Y\n\x14GetValidatorByNumber\x12#.pactus.GetValidatorByNumberRequest\x1a\x1c.pactus.GetValidatorResponse\x12\x64\n\x15GetValidatorAddresses\x12$.pactus.GetValidatorAddressesRequest\x1a%.pactus.GetValidatorAddressesResponse\x12I\n\x0cGetPublicKey\x12\x1b.pactus.GetPublicKeyRequest\x1a\x1c.pactus.GetPublicKeyResponse\x12U\n\x10GetTxPoolContent\x12\x1f.pactus.GetTxPoolContentRequest\x1a .pactus.GetTxPoolContentResponseBE\n\x11pactus.blockchainZ0github.com/pactus-project/pactus/www/grpc/pactusb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x62lockchain.proto\x12\x06pactus\x1a\x11transaction.proto\"-\n\x11GetAccountRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"C\n\x12GetAccountResponse\x12-\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x13.pactus.AccountInfoR\x07\x61\x63\x63ount\"\x1e\n\x1cGetValidatorAddressesRequest\"=\n\x1dGetValidatorAddressesResponse\x12\x1c\n\taddresses\x18\x01 \x03(\tR\taddresses\"/\n\x13GetValidatorRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x1bGetValidatorByNumberRequest\x12\x16\n\x06number\x18\x01 \x01(\x05R\x06number\"K\n\x14GetValidatorResponse\x12\x33\n\tvalidator\x18\x01 \x01(\x0b\x32\x15.pactus.ValidatorInfoR\tvalidator\"/\n\x13GetPublicKeyRequest\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\"5\n\x14GetPublicKeyResponse\x12\x1d\n\npublic_key\x18\x01 \x01(\tR\tpublicKey\"_\n\x0fGetBlockRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x34\n\tverbosity\x18\x02 \x01(\x0e\x32\x16.pactus.BlockVerbosityR\tverbosity\"\x83\x02\n\x10GetBlockResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\x12\x12\n\x04hash\x18\x02 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x03 \x01(\tR\x04\x64\x61ta\x12\x1d\n\nblock_time\x18\x04 \x01(\rR\tblockTime\x12/\n\x06header\x18\x05 \x01(\x0b\x32\x17.pactus.BlockHeaderInfoR\x06header\x12\x34\n\tprev_cert\x18\x06 \x01(\x0b\x32\x17.pactus.CertificateInfoR\x08prevCert\x12)\n\x03txs\x18\x07 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"-\n\x13GetBlockHashRequest\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"*\n\x14GetBlockHashResponse\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"+\n\x15GetBlockHeightRequest\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\"0\n\x16GetBlockHeightResponse\x12\x16\n\x06height\x18\x01 \x01(\rR\x06height\"\x1a\n\x18GetBlockchainInfoRequest\"\xc1\x03\n\x19GetBlockchainInfoResponse\x12*\n\x11last_block_height\x18\x01 \x01(\rR\x0flastBlockHeight\x12&\n\x0flast_block_hash\x18\x02 \x01(\tR\rlastBlockHash\x12%\n\x0etotal_accounts\x18\x03 \x01(\x05R\rtotalAccounts\x12)\n\x10total_validators\x18\x04 \x01(\x05R\x0ftotalValidators\x12\x1f\n\x0btotal_power\x18\x05 \x01(\x03R\ntotalPower\x12\'\n\x0f\x63ommittee_power\x18\x06 \x01(\x03R\x0e\x63ommitteePower\x12H\n\x14\x63ommittee_validators\x18\x07 \x03(\x0b\x32\x15.pactus.ValidatorInfoR\x13\x63ommitteeValidators\x12\x1b\n\tis_pruned\x18\x08 \x01(\x08R\x08isPruned\x12%\n\x0epruning_height\x18\t \x01(\rR\rpruningHeight\x12&\n\x0flast_block_time\x18\n \x01(\x03R\rlastBlockTime\"\x19\n\x17GetConsensusInfoRequest\"O\n\x18GetConsensusInfoResponse\x12\x33\n\tinstances\x18\x01 \x03(\x0b\x32\x15.pactus.ConsensusInfoR\tinstances\"Q\n\x17GetTxPoolContentRequest\x12\x36\n\x0cpayload_type\x18\x01 \x01(\x0e\x32\x13.pactus.PayloadTypeR\x0bpayloadType\"E\n\x18GetTxPoolContentResponse\x12)\n\x03txs\x18\x01 \x03(\x0b\x32\x17.pactus.TransactionInfoR\x03txs\"\xdc\x02\n\rValidatorInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x1d\n\npublic_key\x18\x03 \x01(\tR\tpublicKey\x12\x16\n\x06number\x18\x04 \x01(\x05R\x06number\x12\x14\n\x05stake\x18\x05 \x01(\x03R\x05stake\x12.\n\x13last_bonding_height\x18\x06 \x01(\rR\x11lastBondingHeight\x12\x32\n\x15last_sortition_height\x18\x07 \x01(\rR\x13lastSortitionHeight\x12)\n\x10unbonding_height\x18\x08 \x01(\rR\x0funbondingHeight\x12\x18\n\x07\x61\x64\x64ress\x18\t \x01(\tR\x07\x61\x64\x64ress\x12-\n\x12\x61vailability_score\x18\n \x01(\x01R\x11\x61vailabilityScore\"\x81\x01\n\x0b\x41\x63\x63ountInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x12\n\x04\x64\x61ta\x18\x02 \x01(\tR\x04\x64\x61ta\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x18\n\x07\x62\x61lance\x18\x04 \x01(\x03R\x07\x62\x61lance\x12\x18\n\x07\x61\x64\x64ress\x18\x05 \x01(\tR\x07\x61\x64\x64ress\"\xc4\x01\n\x0f\x42lockHeaderInfo\x12\x18\n\x07version\x18\x01 \x01(\x05R\x07version\x12&\n\x0fprev_block_hash\x18\x02 \x01(\tR\rprevBlockHash\x12\x1d\n\nstate_root\x18\x03 \x01(\tR\tstateRoot\x12%\n\x0esortition_seed\x18\x04 \x01(\tR\rsortitionSeed\x12)\n\x10proposer_address\x18\x05 \x01(\tR\x0fproposerAddress\"\x97\x01\n\x0f\x43\x65rtificateInfo\x12\x12\n\x04hash\x18\x01 \x01(\tR\x04hash\x12\x14\n\x05round\x18\x02 \x01(\x05R\x05round\x12\x1e\n\ncommitters\x18\x03 \x03(\x05R\ncommitters\x12\x1c\n\tabsentees\x18\x04 \x03(\x05R\tabsentees\x12\x1c\n\tsignature\x18\x05 \x01(\tR\tsignature\"\xb1\x01\n\x08VoteInfo\x12$\n\x04type\x18\x01 \x01(\x0e\x32\x10.pactus.VoteTypeR\x04type\x12\x14\n\x05voter\x18\x02 \x01(\tR\x05voter\x12\x1d\n\nblock_hash\x18\x03 \x01(\tR\tblockHash\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12\x19\n\x08\x63p_round\x18\x05 \x01(\x05R\x07\x63pRound\x12\x19\n\x08\x63p_value\x18\x06 \x01(\x05R\x07\x63pValue\"\x97\x01\n\rConsensusInfo\x12\x18\n\x07\x61\x64\x64ress\x18\x01 \x01(\tR\x07\x61\x64\x64ress\x12\x16\n\x06\x61\x63tive\x18\x02 \x01(\x08R\x06\x61\x63tive\x12\x16\n\x06height\x18\x03 \x01(\rR\x06height\x12\x14\n\x05round\x18\x04 \x01(\x05R\x05round\x12&\n\x05votes\x18\x05 \x03(\x0b\x32\x10.pactus.VoteInfoR\x05votes*H\n\x0e\x42lockVerbosity\x12\x0e\n\nBLOCK_DATA\x10\x00\x12\x0e\n\nBLOCK_INFO\x10\x01\x12\x16\n\x12\x42LOCK_TRANSACTIONS\x10\x02*\\\n\x08VoteType\x12\x10\n\x0cVOTE_UNKNOWN\x10\x00\x12\x10\n\x0cVOTE_PREPARE\x10\x01\x12\x12\n\x0eVOTE_PRECOMMIT\x10\x02\x12\x18\n\x14VOTE_CHANGE_PROPOSER\x10\x03\x32\x8b\x07\n\nBlockchain\x12=\n\x08GetBlock\x12\x17.pactus.GetBlockRequest\x1a\x18.pactus.GetBlockResponse\x12I\n\x0cGetBlockHash\x12\x1b.pactus.GetBlockHashRequest\x1a\x1c.pactus.GetBlockHashResponse\x12O\n\x0eGetBlockHeight\x12\x1d.pactus.GetBlockHeightRequest\x1a\x1e.pactus.GetBlockHeightResponse\x12X\n\x11GetBlockchainInfo\x12 .pactus.GetBlockchainInfoRequest\x1a!.pactus.GetBlockchainInfoResponse\x12U\n\x10GetConsensusInfo\x12\x1f.pactus.GetConsensusInfoRequest\x1a .pactus.GetConsensusInfoResponse\x12\x43\n\nGetAccount\x12\x19.pactus.GetAccountRequest\x1a\x1a.pactus.GetAccountResponse\x12I\n\x0cGetValidator\x12\x1b.pactus.GetValidatorRequest\x1a\x1c.pactus.GetValidatorResponse\x12Y\n\x14GetValidatorByNumber\x12#.pactus.GetValidatorByNumberRequest\x1a\x1c.pactus.GetValidatorResponse\x12\x64\n\x15GetValidatorAddresses\x12$.pactus.GetValidatorAddressesRequest\x1a%.pactus.GetValidatorAddressesResponse\x12I\n\x0cGetPublicKey\x12\x1b.pactus.GetPublicKeyRequest\x1a\x1c.pactus.GetPublicKeyResponse\x12U\n\x10GetTxPoolContent\x12\x1f.pactus.GetTxPoolContentRequest\x1a .pactus.GetTxPoolContentResponseBE\n\x11pactus.blockchainZ0github.com/pactus-project/pactus/www/grpc/pactusb\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'blockchain_pb2', globals()) @@ -22,10 +22,10 @@ DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\021pactus.blockchainZ0github.com/pactus-project/pactus/www/grpc/pactus' - _BLOCKVERBOSITY._serialized_start=2960 - _BLOCKVERBOSITY._serialized_end=3032 - _VOTETYPE._serialized_start=3034 - _VOTETYPE._serialized_end=3126 + _BLOCKVERBOSITY._serialized_start=3000 + _BLOCKVERBOSITY._serialized_end=3072 + _VOTETYPE._serialized_start=3074 + _VOTETYPE._serialized_end=3166 _GETACCOUNTREQUEST._serialized_start=47 _GETACCOUNTREQUEST._serialized_end=92 _GETACCOUNTRESPONSE._serialized_start=94 @@ -59,27 +59,27 @@ _GETBLOCKCHAININFOREQUEST._serialized_start=1088 _GETBLOCKCHAININFOREQUEST._serialized_end=1114 _GETBLOCKCHAININFORESPONSE._serialized_start=1117 - _GETBLOCKCHAININFORESPONSE._serialized_end=1526 - _GETCONSENSUSINFOREQUEST._serialized_start=1528 - _GETCONSENSUSINFOREQUEST._serialized_end=1553 - _GETCONSENSUSINFORESPONSE._serialized_start=1555 - _GETCONSENSUSINFORESPONSE._serialized_end=1634 - _GETTXPOOLCONTENTREQUEST._serialized_start=1636 - _GETTXPOOLCONTENTREQUEST._serialized_end=1717 - _GETTXPOOLCONTENTRESPONSE._serialized_start=1719 - _GETTXPOOLCONTENTRESPONSE._serialized_end=1788 - _VALIDATORINFO._serialized_start=1791 - _VALIDATORINFO._serialized_end=2139 - _ACCOUNTINFO._serialized_start=2142 - _ACCOUNTINFO._serialized_end=2271 - _BLOCKHEADERINFO._serialized_start=2274 - _BLOCKHEADERINFO._serialized_end=2470 - _CERTIFICATEINFO._serialized_start=2473 - _CERTIFICATEINFO._serialized_end=2624 - _VOTEINFO._serialized_start=2627 - _VOTEINFO._serialized_end=2804 - _CONSENSUSINFO._serialized_start=2807 - _CONSENSUSINFO._serialized_end=2958 - _BLOCKCHAIN._serialized_start=3129 - _BLOCKCHAIN._serialized_end=4036 + _GETBLOCKCHAININFORESPONSE._serialized_end=1566 + _GETCONSENSUSINFOREQUEST._serialized_start=1568 + _GETCONSENSUSINFOREQUEST._serialized_end=1593 + _GETCONSENSUSINFORESPONSE._serialized_start=1595 + _GETCONSENSUSINFORESPONSE._serialized_end=1674 + _GETTXPOOLCONTENTREQUEST._serialized_start=1676 + _GETTXPOOLCONTENTREQUEST._serialized_end=1757 + _GETTXPOOLCONTENTRESPONSE._serialized_start=1759 + _GETTXPOOLCONTENTRESPONSE._serialized_end=1828 + _VALIDATORINFO._serialized_start=1831 + _VALIDATORINFO._serialized_end=2179 + _ACCOUNTINFO._serialized_start=2182 + _ACCOUNTINFO._serialized_end=2311 + _BLOCKHEADERINFO._serialized_start=2314 + _BLOCKHEADERINFO._serialized_end=2510 + _CERTIFICATEINFO._serialized_start=2513 + _CERTIFICATEINFO._serialized_end=2664 + _VOTEINFO._serialized_start=2667 + _VOTEINFO._serialized_end=2844 + _CONSENSUSINFO._serialized_start=2847 + _CONSENSUSINFO._serialized_end=2998 + _BLOCKCHAIN._serialized_start=3169 + _BLOCKCHAIN._serialized_end=4076 # @@protoc_insertion_point(module_scope) diff --git a/www/grpc/gen/rust/pactus.rs b/www/grpc/gen/rust/pactus.rs index f6523c3c8..36ce81831 100644 --- a/www/grpc/gen/rust/pactus.rs +++ b/www/grpc/gen/rust/pactus.rs @@ -532,6 +532,9 @@ pub struct GetBlockchainInfoResponse { /// Lowest-height block stored (only present if pruning is enabled) #[prost(uint32, tag="9")] pub pruning_height: u32, + /// The last block time as timestamp + #[prost(int64, tag="10")] + pub last_block_time: i64, } /// Message to request consensus information. #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/www/grpc/gen/rust/pactus.serde.rs b/www/grpc/gen/rust/pactus.serde.rs index 613b9a562..8b34387f3 100644 --- a/www/grpc/gen/rust/pactus.serde.rs +++ b/www/grpc/gen/rust/pactus.serde.rs @@ -2879,6 +2879,9 @@ impl serde::Serialize for GetBlockchainInfoResponse { if self.pruning_height != 0 { len += 1; } + if self.last_block_time != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("pactus.GetBlockchainInfoResponse", len)?; if self.last_block_height != 0 { struct_ser.serialize_field("lastBlockHeight", &self.last_block_height)?; @@ -2907,6 +2910,9 @@ impl serde::Serialize for GetBlockchainInfoResponse { if self.pruning_height != 0 { struct_ser.serialize_field("pruningHeight", &self.pruning_height)?; } + if self.last_block_time != 0 { + struct_ser.serialize_field("lastBlockTime", ToString::to_string(&self.last_block_time).as_str())?; + } struct_ser.end() } } @@ -2935,6 +2941,8 @@ impl<'de> serde::Deserialize<'de> for GetBlockchainInfoResponse { "isPruned", "pruning_height", "pruningHeight", + "last_block_time", + "lastBlockTime", ]; #[allow(clippy::enum_variant_names)] @@ -2948,6 +2956,7 @@ impl<'de> serde::Deserialize<'de> for GetBlockchainInfoResponse { CommitteeValidators, IsPruned, PruningHeight, + LastBlockTime, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -2978,6 +2987,7 @@ impl<'de> serde::Deserialize<'de> for GetBlockchainInfoResponse { "committeeValidators" | "committee_validators" => Ok(GeneratedField::CommitteeValidators), "isPruned" | "is_pruned" => Ok(GeneratedField::IsPruned), "pruningHeight" | "pruning_height" => Ok(GeneratedField::PruningHeight), + "lastBlockTime" | "last_block_time" => Ok(GeneratedField::LastBlockTime), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -3006,6 +3016,7 @@ impl<'de> serde::Deserialize<'de> for GetBlockchainInfoResponse { let mut committee_validators__ = None; let mut is_pruned__ = None; let mut pruning_height__ = None; + let mut last_block_time__ = None; while let Some(k) = map.next_key()? { match k { GeneratedField::LastBlockHeight => { @@ -3074,6 +3085,14 @@ impl<'de> serde::Deserialize<'de> for GetBlockchainInfoResponse { Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::LastBlockTime => { + if last_block_time__.is_some() { + return Err(serde::de::Error::duplicate_field("lastBlockTime")); + } + last_block_time__ = + Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } } } Ok(GetBlockchainInfoResponse { @@ -3086,6 +3105,7 @@ impl<'de> serde::Deserialize<'de> for GetBlockchainInfoResponse { committee_validators: committee_validators__.unwrap_or_default(), is_pruned: is_pruned__.unwrap_or_default(), pruning_height: pruning_height__.unwrap_or_default(), + last_block_time: last_block_time__.unwrap_or_default(), }) } } diff --git a/www/grpc/proto/blockchain.proto b/www/grpc/proto/blockchain.proto index b79d670b9..95e58efc4 100644 --- a/www/grpc/proto/blockchain.proto +++ b/www/grpc/proto/blockchain.proto @@ -177,6 +177,8 @@ message GetBlockchainInfoResponse { bool is_pruned = 8; // Lowest-height block stored (only present if pruning is enabled) uint32 pruning_height = 9; + // Timestamp of the last block in Unix format + int64 last_block_time = 10; } // Message to request consensus information. diff --git a/www/grpc/swagger-ui/pactus.swagger.json b/www/grpc/swagger-ui/pactus.swagger.json index 7fd39a564..a5f84b7c6 100644 --- a/www/grpc/swagger-ui/pactus.swagger.json +++ b/www/grpc/swagger-ui/pactus.swagger.json @@ -1624,6 +1624,11 @@ "type": "integer", "format": "int64", "title": "Lowest-height block stored (only present if pruning is enabled)" + }, + "lastBlockTime": { + "type": "string", + "format": "int64", + "title": "The last block time as timestamp" } }, "description": "Message containing the response with general blockchain information." From 18a084be41dbd329c05cea914990368deb3792b0 Mon Sep 17 00:00:00 2001 From: b00f Date: Mon, 22 Jul 2024 00:20:04 +0800 Subject: [PATCH 4/6] refactor(execution): simplify executors and tests (#1425) --- cmd/daemon/init.go | 2 + execution/errors.go | 26 +- execution/execution.go | 65 ++--- execution/execution_test.go | 382 ++++++++++++++++++--------- execution/executor/bond.go | 125 ++++----- execution/executor/bond_test.go | 230 ++++++---------- execution/executor/errors.go | 96 ++++++- execution/executor/executor.go | 35 +++ execution/executor/executor_test.go | 70 +++++ execution/executor/sortition.go | 93 +++---- execution/executor/sortition_test.go | 195 +++++++------- execution/executor/transfer.go | 56 ++-- execution/executor/transfer_test.go | 79 ++---- execution/executor/unbond.go | 58 ++-- execution/executor/unbond_test.go | 99 +++---- execution/executor/withdraw.go | 68 +++-- execution/executor/withdraw_test.go | 99 ++++--- sandbox/sandbox_test.go | 27 +- state/execution.go | 17 +- state/execution_test.go | 12 +- state/state.go | 7 +- txpool/txpool.go | 4 +- txpool/txpool_test.go | 2 +- util/errors/errors.go | 2 - util/testsuite/testsuite.go | 41 +-- 25 files changed, 1053 insertions(+), 837 deletions(-) create mode 100644 execution/executor/executor.go create mode 100644 execution/executor/executor_test.go diff --git a/cmd/daemon/init.go b/cmd/daemon/init.go index ad4f0e631..24634eab9 100644 --- a/cmd/daemon/init.go +++ b/cmd/daemon/init.go @@ -45,9 +45,11 @@ func buildInitCmd(parentCmd *cobra.Command) { return } + var mnemonic string if *restoreOpt == "" { mnemonic, _ = wallet.GenerateMnemonic(*entropyOpt) + cmd.PrintLine() cmd.PrintInfoMsgf("Your wallet seed is:") cmd.PrintInfoMsgBoldf(" " + mnemonic) diff --git a/execution/errors.go b/execution/errors.go index 9f435a7d6..79a5f850d 100644 --- a/execution/errors.go +++ b/execution/errors.go @@ -6,7 +6,6 @@ import ( "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/types/tx/payload" ) // TransactionCommittedError is returned when an attempt is made @@ -22,36 +21,25 @@ func (e TransactionCommittedError) Error() string { e.ID.String()) } -// UnknownPayloadTypeError is returned when transaction payload type -// is not valid. -type UnknownPayloadTypeError struct { - PayloadType payload.Type -} - -func (e UnknownPayloadTypeError) Error() string { - return fmt.Sprintf("unknown payload type: %s", - e.PayloadType.String()) -} - -// PastLockTimeError is returned when the lock time of a transaction +// LockTimeExpiredError is returned when the lock time of a transaction // is in the past and has expired, // indicating the transaction can no longer be executed. -type PastLockTimeError struct { +type LockTimeExpiredError struct { LockTime uint32 } -func (e PastLockTimeError) Error() string { - return fmt.Sprintf("lock time is in the past: %v", e.LockTime) +func (e LockTimeExpiredError) Error() string { + return fmt.Sprintf("lock time expired: %v", e.LockTime) } -// FutureLockTimeError is returned when the lock time of a transaction +// LockTimeInFutureError is returned when the lock time of a transaction // is in the future, // indicating the transaction is not yet eligible for processing. -type FutureLockTimeError struct { +type LockTimeInFutureError struct { LockTime uint32 } -func (e FutureLockTimeError) Error() string { +func (e LockTimeInFutureError) Error() string { return fmt.Sprintf("lock time is in the future: %v", e.LockTime) } diff --git a/execution/execution.go b/execution/execution.go index dd43d92ed..8ec1c326e 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -4,40 +4,26 @@ import ( "github.com/pactus-project/pactus/execution/executor" "github.com/pactus-project/pactus/sandbox" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/types/tx/payload" ) -type Executor interface { - Execute(trx *tx.Tx, sb sandbox.Sandbox) error -} -type Execution struct { - executors map[payload.Type]Executor - strict bool -} - -func newExecution(strict bool) *Execution { - execs := make(map[payload.Type]Executor) - execs[payload.TypeTransfer] = executor.NewTransferExecutor(strict) - execs[payload.TypeBond] = executor.NewBondExecutor(strict) - execs[payload.TypeSortition] = executor.NewSortitionExecutor(strict) - execs[payload.TypeUnbond] = executor.NewUnbondExecutor(strict) - execs[payload.TypeWithdraw] = executor.NewWithdrawExecutor(strict) - - return &Execution{ - executors: execs, - strict: strict, +func Execute(trx *tx.Tx, sb sandbox.Sandbox) error { + exe, err := executor.MakeExecutor(trx, sb) + if err != nil { + return err } -} -func NewExecutor() *Execution { - return newExecution(true) -} + exe.Execute() + sb.CommitTransaction(trx) -func NewChecker() *Execution { - return newExecution(false) + return nil } -func (exe *Execution) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { +func CheckAndExecute(trx *tx.Tx, sb sandbox.Sandbox, strict bool) error { + exe, err := executor.MakeExecutor(trx, sb) + if err != nil { + return err + } + if sb.IsBanned(trx.Payload().Signer()) { return SignerBannedError{ addr: trx.Payload().Signer(), @@ -50,31 +36,25 @@ func (exe *Execution) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { } } - if err := exe.checkLockTime(trx, sb); err != nil { + if err := CheckLockTime(trx, sb, strict); err != nil { return err } - if err := exe.checkFee(trx); err != nil { + if err := CheckFee(trx); err != nil { return err } - e, ok := exe.executors[trx.Payload().Type()] - if !ok { - return UnknownPayloadTypeError{ - PayloadType: trx.Payload().Type(), - } - } - - if err := e.Execute(trx, sb); err != nil { + if err := exe.Check(strict); err != nil { return err } + exe.Execute() sb.CommitTransaction(trx) return nil } -func (exe *Execution) checkLockTime(trx *tx.Tx, sb sandbox.Sandbox) error { +func CheckLockTime(trx *tx.Tx, sb sandbox.Sandbox, strict bool) error { interval := sb.Params().TransactionToLiveInterval if trx.IsSubsidyTx() { @@ -85,18 +65,18 @@ func (exe *Execution) checkLockTime(trx *tx.Tx, sb sandbox.Sandbox) error { if sb.CurrentHeight() > interval { if trx.LockTime() < sb.CurrentHeight()-interval { - return PastLockTimeError{ + return LockTimeExpiredError{ LockTime: trx.LockTime(), } } } - if exe.strict { + if strict { // In strict mode, transactions with future lock times are rejected. // In non-strict mode, they are added to the transaction pool and // processed once eligible. if trx.LockTime() > sb.CurrentHeight() { - return FutureLockTimeError{ + return LockTimeInFutureError{ LockTime: trx.LockTime(), } } @@ -105,7 +85,8 @@ func (exe *Execution) checkLockTime(trx *tx.Tx, sb sandbox.Sandbox) error { return nil } -func (*Execution) checkFee(trx *tx.Tx) error { +func CheckFee(trx *tx.Tx) error { + // TODO: This check maybe can be done in BasicCheck? if trx.IsFreeTx() { if trx.Fee() != 0 { return InvalidFeeError{ diff --git a/execution/execution_test.go b/execution/execution_test.go index 352683143..6bff7d6f9 100644 --- a/execution/execution_test.go +++ b/execution/execution_test.go @@ -4,195 +4,325 @@ import ( "testing" "github.com/pactus-project/pactus/crypto" + "github.com/pactus-project/pactus/execution/executor" "github.com/pactus-project/pactus/sandbox" + "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/util/errors" "github.com/pactus-project/pactus/util/testsuite" "github.com/stretchr/testify/assert" ) -func TestLockTime(t *testing.T) { +func TestTransferLockTime(t *testing.T) { ts := testsuite.NewTestSuite(t) sb := sandbox.MockingSandbox(ts) - sb.TestAcceptSortition = true - exe := NewExecutor() rndPubKey, rndPrvKey := ts.RandBLSKeyPair() rndAccAddr := rndPubKey.AccountAddress() rndAcc := sb.MakeNewAccount(rndAccAddr) - rndAcc.AddToBalance(100 * 1e9) + rndAcc.AddToBalance(1000 * 1e9) sb.UpdateAccount(rndAccAddr, rndAcc) + _ = sb.TestStore.AddTestBlock(8642) + + tests := []struct { + name string + lockTime uint32 + strictErr error + nonStrictErr error + }{ + { + name: "Transaction has expired LockTime (-8641)", + lockTime: sb.CurrentHeight() - sb.TestParams.TransactionToLiveInterval - 1, + strictErr: LockTimeExpiredError{sb.CurrentHeight() - sb.TestParams.TransactionToLiveInterval - 1}, + nonStrictErr: LockTimeExpiredError{sb.CurrentHeight() - sb.TestParams.TransactionToLiveInterval - 1}, + }, + { + name: "Transaction has valid LockTime (-8640)", + lockTime: sb.CurrentHeight() - sb.TestParams.TransactionToLiveInterval, + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Transaction has valid LockTime (-88)", + lockTime: sb.CurrentHeight() - 88, + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Transaction has valid LockTime (0)", + lockTime: sb.CurrentHeight(), + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Transaction has future LockTime (+1)", + lockTime: sb.CurrentHeight() + 1, + strictErr: LockTimeInFutureError{sb.CurrentHeight() + 1}, + nonStrictErr: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + trx := tx.NewTransferTx(tc.lockTime, rndAccAddr, ts.RandAccAddress(), 1000, 1000, "") + ts.HelperSignTransaction(rndPrvKey, trx) + + strictErr := CheckLockTime(trx, sb, true) + assert.ErrorIs(t, strictErr, tc.strictErr) + + nonStrictErr := CheckLockTime(trx, sb, false) + assert.ErrorIs(t, nonStrictErr, tc.nonStrictErr) + }) + } +} + +func TestSortitionLockTime(t *testing.T) { + ts := testsuite.NewTestSuite(t) + + sb := sandbox.MockingSandbox(ts) + sb.TestAcceptSortition = true + rndPubKey, rndPrvKey := ts.RandBLSKeyPair() rndValAddr := rndPubKey.ValidatorAddress() rndVal := sb.MakeNewValidator(rndPubKey) - rndVal.AddToStake(100 * 1e9) + rndVal.AddToStake(1000 * 1e9) sb.UpdateValidator(rndVal) _ = sb.TestStore.AddTestBlock(8642) - t.Run("Future LockTime, Should return error (+1)", func(t *testing.T) { - lockTime := sb.CurrentHeight() + 1 - trx := tx.NewTransferTx(lockTime, rndAccAddr, ts.RandAccAddress(), 1000, 1000, "future-lockTime") - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.ErrorIs(t, err, FutureLockTimeError{LockTime: lockTime}) - }) + tests := []struct { + name string + lockTime uint32 + strictErr error + nonStrictErr error + }{ + { + name: "Sortition transaction has expired LockTime (-8)", + lockTime: sb.CurrentHeight() - sb.TestParams.SortitionInterval - 1, + strictErr: LockTimeExpiredError{sb.CurrentHeight() - sb.TestParams.SortitionInterval - 1}, + nonStrictErr: LockTimeExpiredError{sb.CurrentHeight() - sb.TestParams.SortitionInterval - 1}, + }, + { + name: "Sortition transaction has valid LockTime (-7)", + lockTime: sb.CurrentHeight() - sb.TestParams.SortitionInterval, + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Sortition transaction has valid LockTime (-1)", + lockTime: sb.CurrentHeight() - 1, + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Sortition transaction has valid LockTime (0)", + lockTime: sb.CurrentHeight(), + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Sortition transaction has future LockTime (+1)", + lockTime: sb.CurrentHeight() + 1, + strictErr: LockTimeInFutureError{sb.CurrentHeight() + 1}, + nonStrictErr: nil, + }, + } - t.Run("Past LockTime, Should return error (-8641)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - sb.TestParams.TransactionToLiveInterval - 1 - trx := tx.NewTransferTx(lockTime, rndAccAddr, ts.RandAccAddress(), 1000, 1000, "past-lockTime") - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.ErrorIs(t, err, PastLockTimeError{LockTime: lockTime}) - }) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + trx := tx.NewSortitionTx(tc.lockTime, rndValAddr, ts.RandProof()) + ts.HelperSignTransaction(rndPrvKey, trx) - t.Run("Transaction has valid LockTime (-8640)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - sb.TestParams.TransactionToLiveInterval - trx := tx.NewTransferTx(lockTime, rndAccAddr, ts.RandAccAddress(), 1000, 1000, "ok") - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.NoError(t, err) - }) + strictErr := CheckLockTime(trx, sb, true) + assert.ErrorIs(t, strictErr, tc.strictErr) - t.Run("Transaction has valid LockTime (0)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - trx := tx.NewTransferTx(lockTime, rndAccAddr, ts.RandAccAddress(), 1000, 1000, "ok") - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.NoError(t, err) - }) + nonStrictErr := CheckLockTime(trx, sb, false) + assert.ErrorIs(t, nonStrictErr, tc.nonStrictErr) + }) + } +} - t.Run("Subsidy transaction has invalid LockTime (+1)", func(t *testing.T) { - lockTime := sb.CurrentHeight() + 1 - trx := tx.NewSubsidyTx(lockTime, ts.RandAccAddress(), 1000, - "invalid-lockTime") - err := exe.Execute(trx, sb) - assert.ErrorIs(t, err, FutureLockTimeError{LockTime: lockTime}) - }) +func TestSubsidyLockTime(t *testing.T) { + ts := testsuite.NewTestSuite(t) - t.Run("Subsidy transaction has invalid LockTime (-1)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - 1 - trx := tx.NewSubsidyTx(lockTime, ts.RandAccAddress(), 1000, - "invalid-lockTime") - err := exe.Execute(trx, sb) - assert.ErrorIs(t, err, PastLockTimeError{LockTime: lockTime}) - }) + sb := sandbox.MockingSandbox(ts) + _ = sb.TestStore.AddTestBlock(8642) - t.Run("Subsidy transaction has valid LockTime (0)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - trx := tx.NewSubsidyTx(lockTime, ts.RandAccAddress(), 1000, "ok") - err := exe.Execute(trx, sb) - assert.NoError(t, err) - }) + tests := []struct { + name string + lockTime uint32 + strictErr error + nonStrictErr error + }{ + { + name: "Subsidy transaction has expired LockTime (-1)", + lockTime: sb.CurrentHeight() - 1, + strictErr: LockTimeExpiredError{sb.CurrentHeight() - 1}, + nonStrictErr: LockTimeExpiredError{sb.CurrentHeight() - 1}, + }, + { + name: "Subsidy transaction has valid LockTime (0)", + lockTime: sb.CurrentHeight(), + strictErr: nil, + nonStrictErr: nil, + }, + { + name: "Subsidy transaction has future LockTime (+1)", + lockTime: sb.CurrentHeight() + 1, + strictErr: LockTimeInFutureError{sb.CurrentHeight() + 1}, + nonStrictErr: nil, + }, + } - t.Run("Sortition transaction has invalid LockTime (+1)", func(t *testing.T) { - lockTime := sb.CurrentHeight() + 1 - proof := ts.RandProof() - trx := tx.NewSortitionTx(lockTime, rndValAddr, proof) - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.ErrorIs(t, err, FutureLockTimeError{LockTime: lockTime}) - }) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + trx := tx.NewSubsidyTx(tc.lockTime, ts.RandAccAddress(), 1000, "subsidy-test") - t.Run("Sortition transaction has invalid LockTime (-8)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - sb.TestParams.SortitionInterval - 1 - proof := ts.RandProof() - trx := tx.NewSortitionTx(lockTime, rndValAddr, proof) - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.ErrorIs(t, err, PastLockTimeError{LockTime: lockTime}) - }) + strictErr := CheckLockTime(trx, sb, true) + assert.ErrorIs(t, strictErr, tc.strictErr) - t.Run("Sortition transaction has valid LockTime (-7)", func(t *testing.T) { - lockTime := sb.CurrentHeight() - sb.TestParams.SortitionInterval - proof := ts.RandProof() + nonStrictErr := CheckLockTime(trx, sb, false) + assert.ErrorIs(t, nonStrictErr, tc.nonStrictErr) + }) + } +} - trx := tx.NewSortitionTx(lockTime, rndValAddr, proof) - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.NoError(t, err) - }) +func TestCheckFee(t *testing.T) { + ts := testsuite.NewTestSuite(t) + + tests := []struct { + name string + trx *tx.Tx + expectedErr error + }{ + { + name: "Subsidy transaction with fee", + trx: tx.NewTransferTx(ts.RandHeight(), crypto.TreasuryAddress, ts.RandAccAddress(), + ts.RandAmount(), 1, ""), + expectedErr: InvalidFeeError{Fee: 1, Expected: 0}, + }, + { + name: "Subsidy transaction without fee", + trx: tx.NewTransferTx(ts.RandHeight(), crypto.TreasuryAddress, ts.RandAccAddress(), + ts.RandAmount(), 0, ""), + expectedErr: nil, + }, + { + name: "Transfer transaction with fee", + trx: tx.NewTransferTx(ts.RandHeight(), ts.RandAccAddress(), ts.RandAccAddress(), + ts.RandAmount(), 0, ""), + expectedErr: nil, + }, + { + name: "Transfer transaction without fee", + trx: tx.NewTransferTx(ts.RandHeight(), ts.RandAccAddress(), ts.RandAccAddress(), + ts.RandAmount(), 0, ""), + expectedErr: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := CheckFee(tc.trx) + assert.ErrorIs(t, err, tc.expectedErr) + }) + } } -func TestExecution(t *testing.T) { +func TestExecute(t *testing.T) { ts := testsuite.NewTestSuite(t) sb := sandbox.MockingSandbox(ts) - exe := NewExecutor() - - rndPubKey, rndPrvKey := ts.RandBLSKeyPair() - rndAccAddr := rndPubKey.AccountAddress() - rndValAddr := rndPubKey.ValidatorAddress() - rndAcc := sb.MakeNewAccount(rndAccAddr) - rndAcc.AddToBalance(100 * 1e9) - sb.UpdateAccount(rndAccAddr, rndAcc) _ = sb.TestStore.AddTestBlock(8642) lockTime := sb.CurrentHeight() - t.Run("Invalid transaction, Should returns error", func(t *testing.T) { - trx := tx.NewTransferTx(lockTime, ts.RandAccAddress(), ts.RandAccAddress(), 1000, 0.1e9, "invalid-tx") - err := exe.Execute(trx, sb) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) - }) + t.Run("Invalid transaction, Should return error", func(t *testing.T) { + randAddr := ts.RandAccAddress() + trx := tx.NewTransferTx(lockTime, randAddr, ts.RandAccAddress(), + ts.RandAmount(), ts.RandFee(), "invalid-tx") - t.Run("Invalid fee (subsidy tx), Should returns error", func(t *testing.T) { - trx := tx.NewTransferTx(lockTime, crypto.TreasuryAddress, ts.RandAccAddress(), 1000, 1, "invalid fee") - - expectedErr := InvalidFeeError{Fee: 1, Expected: 0} - assert.ErrorIs(t, exe.Execute(trx, sb), expectedErr) - assert.ErrorIs(t, exe.checkFee(trx), expectedErr) + err := Execute(trx, sb) + assert.ErrorIs(t, err, executor.AccountNotFoundError{Address: randAddr}) }) - t.Run("Execution failed", func(t *testing.T) { - proof := ts.RandProof() - trx := tx.NewSortitionTx(lockTime, rndValAddr, proof) - ts.HelperSignTransaction(rndPrvKey, trx) - err := exe.Execute(trx, sb) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) + t.Run("Ok", func(t *testing.T) { + trx := tx.NewSubsidyTx(lockTime, ts.RandAccAddress(), 1000, "valid-tx") + err := Execute(trx, sb) + assert.NoError(t, err) + + assert.True(t, sb.AnyRecentTransaction(trx.ID())) }) } -func TestReplay(t *testing.T) { +func TestCheck(t *testing.T) { ts := testsuite.NewTestSuite(t) - executor := NewExecutor() sb := sandbox.MockingSandbox(ts) - rndPubKey, rndPrvKey := ts.RandBLSKeyPair() - rndAccAddr := rndPubKey.AccountAddress() - rndAcc := sb.MakeNewAccount(rndAccAddr) - rndAcc.AddToBalance(1e9) - sb.UpdateAccount(rndAccAddr, rndAcc) + _ = sb.TestStore.AddTestBlock(8642) lockTime := sb.CurrentHeight() - trx := tx.NewTransferTx(lockTime, - rndAccAddr, ts.RandAccAddress(), 10000, 1000, "") - ts.HelperSignTransaction(rndPrvKey, trx) + t.Run("Invalid lock-time, Should return error", func(t *testing.T) { + invalidLocoTme := lockTime + 1 + trx := tx.NewTransferTx(invalidLocoTme, crypto.TreasuryAddress, + ts.RandAccAddress(), ts.RandAmount(), 0, "invalid lock-time") - err := executor.Execute(trx, sb) - assert.NoError(t, err) - err = executor.Execute(trx, sb) - assert.ErrorIs(t, err, TransactionCommittedError{ - ID: trx.ID(), + err := CheckAndExecute(trx, sb, true) + assert.ErrorIs(t, err, LockTimeInFutureError{LockTime: invalidLocoTme}) + }) + + t.Run("Invalid fee, Should return error", func(t *testing.T) { + invalidFee := amount.Amount(1) + trx := tx.NewTransferTx(lockTime, crypto.TreasuryAddress, + ts.RandAccAddress(), ts.RandAmount(), invalidFee, "invalid fee") + + err := CheckAndExecute(trx, sb, true) + assert.ErrorIs(t, err, InvalidFeeError{Fee: invalidFee, Expected: 0}) + }) + + t.Run("Invalid transaction, Should return error", func(t *testing.T) { + randAddr := ts.RandAccAddress() + trx := tx.NewTransferTx(lockTime, randAddr, ts.RandAccAddress(), + ts.RandAmount(), ts.RandFee(), "invalid-tx") + + err := CheckAndExecute(trx, sb, true) + assert.ErrorIs(t, err, executor.AccountNotFoundError{Address: randAddr}) + }) + + t.Run("Invalid transaction, Should return error", func(t *testing.T) { + valAddr := sb.TestCommittee.Validators()[0].Address() + sb.TestAcceptSortition = false + trx := tx.NewSortitionTx(lockTime, valAddr, ts.RandProof()) + + err := CheckAndExecute(trx, sb, true) + assert.ErrorIs(t, err, executor.ErrInvalidSortitionProof) + }) + + t.Run("Ok", func(t *testing.T) { + trx := tx.NewSubsidyTx(lockTime, ts.RandAccAddress(), 1000, "valid-tx") + err := CheckAndExecute(trx, sb, true) + assert.NoError(t, err) + + assert.True(t, sb.AnyRecentTransaction(trx.ID())) }) } -func TestChecker(t *testing.T) { +func TestReplay(t *testing.T) { ts := testsuite.NewTestSuite(t) - executor := NewExecutor() - checker := NewChecker() sb := sandbox.MockingSandbox(ts) rndPubKey, rndPrvKey := ts.RandBLSKeyPair() rndAccAddr := rndPubKey.AccountAddress() rndAcc := sb.MakeNewAccount(rndAccAddr) rndAcc.AddToBalance(1e9) sb.UpdateAccount(rndAccAddr, rndAcc) - lockTime := sb.CurrentHeight() + 1 + lockTime := sb.CurrentHeight() trx := tx.NewTransferTx(lockTime, rndAccAddr, ts.RandAccAddress(), 10000, 1000, "") ts.HelperSignTransaction(rndPrvKey, trx) - err := executor.Execute(trx, sb) - assert.ErrorIs(t, err, FutureLockTimeError{LockTime: lockTime}) - err = checker.Execute(trx, sb) + err := Execute(trx, sb) assert.NoError(t, err) + + err = CheckAndExecute(trx, sb, false) + assert.ErrorIs(t, err, TransactionCommittedError{ + ID: trx.ID(), + }) } diff --git a/execution/executor/bond.go b/execution/executor/bond.go index a019c0b00..01449aa85 100644 --- a/execution/executor/bond.go +++ b/execution/executor/bond.go @@ -2,94 +2,101 @@ package executor import ( "github.com/pactus-project/pactus/sandbox" + "github.com/pactus-project/pactus/types/account" + "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/tx/payload" - "github.com/pactus-project/pactus/util/errors" + "github.com/pactus-project/pactus/types/validator" ) type BondExecutor struct { - strict bool + sb sandbox.Sandbox + pld *payload.BondPayload + fee amount.Amount + sender *account.Account + receiver *validator.Validator } -func NewBondExecutor(strict bool) *BondExecutor { - return &BondExecutor{strict: strict} -} - -func (e *BondExecutor) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { +func newBondExecutor(trx *tx.Tx, sb sandbox.Sandbox) (*BondExecutor, error) { pld := trx.Payload().(*payload.BondPayload) - senderAcc := sb.Account(pld.From) - if senderAcc == nil { - return errors.Errorf(errors.ErrInvalidAddress, - "unable to retrieve sender account") + sender := sb.Account(pld.From) + if sender == nil { + return nil, AccountNotFoundError{Address: pld.From} } - receiverVal := sb.Validator(pld.To) - if receiverVal == nil { + receiver := sb.Validator(pld.To) + if receiver == nil { if pld.PublicKey == nil { - return errors.Errorf(errors.ErrInvalidPublicKey, - "public key is not set") + return nil, ErrPublicKeyNotSet } - // TODO: remove me in future - if pld.Stake < sb.Params().MinimumStake { - return errors.Errorf(errors.ErrInvalidTx, - "validator's stake can't be less than %v", sb.Params().MinimumStake) - } - receiverVal = sb.MakeNewValidator(pld.PublicKey) + receiver = sb.MakeNewValidator(pld.PublicKey) } else if pld.PublicKey != nil { - return errors.Errorf(errors.ErrInvalidPublicKey, - "public key is set") + return nil, ErrPublicKeyAlreadySet + } + + return &BondExecutor{ + sb: sb, + pld: pld, + fee: trx.Fee(), + sender: sender, + receiver: receiver, + }, nil +} + +func (e *BondExecutor) Check(strict bool) error { + if e.receiver.UnbondingHeight() > 0 { + return ErrValidatorUnbonded } - if receiverVal.UnbondingHeight() > 0 { - return errors.Errorf(errors.ErrInvalidHeight, - "validator has unbonded at height %v", receiverVal.UnbondingHeight()) + + if e.sender.Balance() < e.pld.Stake+e.fee { + return ErrInsufficientFunds } - if e.strict { + + if e.pld.Stake < e.sb.Params().MinimumStake { + // This check prevents a potential attack where an attacker could send zero + // or a small amount of stake to a full validator, effectively parking the + // validator for the bonding period. + if e.pld.Stake == 0 || e.pld.Stake+e.receiver.Stake() != e.sb.Params().MaximumStake { + return SmallStakeError{ + Minimum: e.sb.Params().MinimumStake, + } + } + } + + if e.receiver.Stake()+e.pld.Stake > e.sb.Params().MaximumStake { + return MaximumStakeError{ + Maximum: e.sb.Params().MaximumStake, + } + } + + if strict { // In strict mode, bond transactions will be rejected if a validator is // already in the committee. // In non-strict mode, they are added to the transaction pool and // processed once eligible. - if sb.Committee().Contains(pld.To) { - return errors.Errorf(errors.ErrInvalidTx, - "validator %v is in committee", pld.To) + if e.sb.Committee().Contains(e.pld.To) { + return ErrValidatorInCommittee } // In strict mode, bond transactions will be rejected if a validator is // going to join the committee in the next height. // In non-strict mode, they are added to the transaction pool and // processed once eligible. - if sb.IsJoinedCommittee(pld.To) { - return errors.Errorf(errors.ErrInvalidTx, - "validator %v joins committee in the next height", pld.To) + if e.sb.IsJoinedCommittee(e.pld.To) { + return ErrValidatorInCommittee } } - if senderAcc.Balance() < pld.Stake+trx.Fee() { - return ErrInsufficientFunds - } - if receiverVal.Stake()+pld.Stake > sb.Params().MaximumStake { - return errors.Errorf(errors.ErrInvalidAmount, - "validator's stake can't be more than %v", sb.Params().MaximumStake) - } - // TODO: remove me in future - // We can have a level for committing blocks even if they are not fully compatible with the current rules. - // However, since they were committed in the past, they should be accepted by new nodes. - if sb.CurrentHeight() > 740_000 { - if pld.Stake < sb.Params().MinimumStake { - if pld.Stake == 0 || receiverVal.Stake()+pld.Stake != sb.Params().MaximumStake { - return errors.Errorf(errors.ErrInvalidTx, - "stake amount should not be less than %v", sb.Params().MinimumStake) - } - } - } - - senderAcc.SubtractFromBalance(pld.Stake + trx.Fee()) - receiverVal.AddToStake(pld.Stake) - receiverVal.UpdateLastBondingHeight(sb.CurrentHeight()) + return nil +} - sb.UpdatePowerDelta(int64(pld.Stake)) - sb.UpdateAccount(pld.From, senderAcc) - sb.UpdateValidator(receiverVal) +func (e *BondExecutor) Execute() { + e.sender.SubtractFromBalance(e.pld.Stake + e.fee) + e.receiver.AddToStake(e.pld.Stake) + e.receiver.UpdateLastBondingHeight(e.sb.CurrentHeight()) - return nil + e.sb.UpdatePowerDelta(int64(e.pld.Stake)) + e.sb.UpdateAccount(e.pld.From, e.sender) + e.sb.UpdateValidator(e.receiver) } diff --git a/execution/executor/bond_test.go b/execution/executor/bond_test.go index b5b4b73f8..80b9532a2 100644 --- a/execution/executor/bond_test.go +++ b/execution/executor/bond_test.go @@ -3,186 +3,129 @@ package executor import ( "testing" - "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/util/errors" "github.com/stretchr/testify/assert" ) func TestExecuteBondTx(t *testing.T) { td := setup(t) - exe := NewBondExecutor(true) senderAddr, senderAcc := td.sandbox.TestStore.RandomTestAcc() senderBalance := senderAcc.Balance() - pub, _ := td.RandBLSKeyPair() - receiverAddr := pub.ValidatorAddress() + valPub, _ := td.RandBLSKeyPair() + receiverAddr := valPub.ValidatorAddress() + amt := td.RandAmountRange( td.sandbox.TestParams.MinimumStake, td.sandbox.TestParams.MaximumStake) fee := td.RandFee() lockTime := td.sandbox.CurrentHeight() - t.Run("Should fail, invalid sender", func(t *testing.T) { - trx := tx.NewBondTx(lockTime, td.RandAccAddress(), - receiverAddr, pub, amt, fee, "invalid sender") + t.Run("Should fail, unknown address", func(t *testing.T) { + randomAddr := td.RandAccAddress() + trx := tx.NewBondTx(lockTime, randomAddr, + receiverAddr, valPub, amt, fee, "unknown address") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) + td.check(t, trx, true, AccountNotFoundError{Address: randomAddr}) + td.check(t, trx, false, AccountNotFoundError{Address: randomAddr}) }) - t.Run("Should fail, treasury address as receiver", func(t *testing.T) { + t.Run("Should fail, public key is not set", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, - crypto.TreasuryAddress, nil, amt, fee, "invalid ") + receiverAddr, nil, amt, fee, "no public key") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidPublicKey) + td.check(t, trx, true, ErrPublicKeyNotSet) + td.check(t, trx, false, ErrPublicKeyNotSet) }) - t.Run("Should fail, insufficient balance", func(t *testing.T) { + t.Run("Should fail, public key should not set for existing validators", func(t *testing.T) { + randPub, _ := td.RandBLSKeyPair() + val := td.sandbox.MakeNewValidator(randPub) + td.sandbox.UpdateValidator(val) + trx := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, pub, senderBalance+1, 0, "insufficient balance") + randPub.ValidatorAddress(), randPub, amt, fee, "with public key") - err := exe.Execute(trx, td.sandbox) - assert.ErrorIs(t, err, ErrInsufficientFunds) + td.check(t, trx, true, ErrPublicKeyAlreadySet) + td.check(t, trx, false, ErrPublicKeyAlreadySet) }) - t.Run("Should fail, inside committee", func(t *testing.T) { - pub0 := td.sandbox.Committee().Proposer(0).PublicKey() + t.Run("Should fail, insufficient balance", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, - pub0.ValidatorAddress(), nil, amt, fee, "inside committee") + receiverAddr, valPub, senderBalance+1, 0, "insufficient balance") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) + td.check(t, trx, true, ErrInsufficientFunds) + td.check(t, trx, false, ErrInsufficientFunds) }) t.Run("Should fail, unbonded before", func(t *testing.T) { - unbondedPub, _ := td.RandBLSKeyPair() - val := td.sandbox.MakeNewValidator(unbondedPub) - val.UpdateLastBondingHeight(1) - val.UpdateUnbondingHeight(td.sandbox.CurrentHeight()) + randPub, _ := td.RandBLSKeyPair() + val := td.sandbox.MakeNewValidator(randPub) + val.UpdateUnbondingHeight(td.RandHeight()) td.sandbox.UpdateValidator(val) trx := tx.NewBondTx(lockTime, senderAddr, - unbondedPub.ValidatorAddress(), nil, amt, fee, "unbonded before") + randPub.ValidatorAddress(), nil, amt, fee, "unbonded before") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidHeight) + td.check(t, trx, true, ErrValidatorUnbonded) + td.check(t, trx, false, ErrValidatorUnbonded) }) - t.Run("Should fail, public key is not set", func(t *testing.T) { + t.Run("Should fail, amount less than MinimumStake", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, nil, amt, fee, "no public key") + receiverAddr, valPub, td.sandbox.TestParams.MinimumStake-1, fee, "less than MinimumStake") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidPublicKey) + td.check(t, trx, true, SmallStakeError{td.sandbox.TestParams.MinimumStake}) + td.check(t, trx, false, SmallStakeError{td.sandbox.TestParams.MinimumStake}) }) - t.Run("Should fail, amount less than MinimumStake", func(t *testing.T) { + t.Run("Should fail, validator's stake exceeds the MaximumStake", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, pub, 1000, fee, "less than MinimumStake") + receiverAddr, valPub, td.sandbox.TestParams.MaximumStake+1, fee, "more than MaximumStake") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.ErrInvalidTx, errors.Code(err)) + td.check(t, trx, true, MaximumStakeError{td.sandbox.TestParams.MaximumStake}) + td.check(t, trx, false, MaximumStakeError{td.sandbox.TestParams.MaximumStake}) }) - t.Run("Ok", func(t *testing.T) { + t.Run("Should fail, inside committee", func(t *testing.T) { + pub0 := td.sandbox.Committee().Proposer(0).PublicKey() trx := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, pub, amt, fee, "ok") + pub0.ValidatorAddress(), nil, amt, fee, "inside committee") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err, "Ok") + td.check(t, trx, true, ErrValidatorInCommittee) + td.check(t, trx, false, nil) }) - t.Run("Should fail, public key should not set for existing validators", func(t *testing.T) { + t.Run("Should fail, joining committee", func(t *testing.T) { + randPub, _ := td.RandBLSKeyPair() + val := td.sandbox.MakeNewValidator(randPub) + td.sandbox.UpdateValidator(val) + td.sandbox.JoinedToCommittee(val.Address()) trx := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, pub, amt, fee, "with public key") + randPub.ValidatorAddress(), nil, amt, fee, "inside committee") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidPublicKey) + td.check(t, trx, true, ErrValidatorInCommittee) + td.check(t, trx, false, nil) }) - assert.Equal(t, td.sandbox.Account(senderAddr).Balance(), senderBalance-(amt+fee)) - assert.Equal(t, td.sandbox.Validator(receiverAddr).Stake(), amt) - assert.Equal(t, td.sandbox.Validator(receiverAddr).LastBondingHeight(), td.sandbox.CurrentHeight()) - assert.Equal(t, td.sandbox.PowerDelta(), int64(amt)) - td.checkTotalCoin(t, fee) -} - -// TestBondInsideCommittee checks if a validator inside the committee attempts to -// increase their stake. -// In non-strict mode it should be accepted. -func TestBondInsideCommittee(t *testing.T) { - td := setup(t) - - exe1 := NewBondExecutor(true) - exe2 := NewBondExecutor(false) - senderAddr, _ := td.sandbox.TestStore.RandomTestAcc() - amt := td.RandAmountRange( - td.sandbox.TestParams.MinimumStake, - td.sandbox.TestParams.MaximumStake-10e9) // it has 10e9 stake - fee := td.RandFee() - lockTime := td.sandbox.CurrentHeight() - - pub := td.sandbox.Committee().Proposer(0).PublicKey() - trx := tx.NewBondTx(lockTime, senderAddr, - pub.ValidatorAddress(), nil, amt, fee, "inside committee") - - assert.Error(t, exe1.Execute(trx, td.sandbox)) - assert.NoError(t, exe2.Execute(trx, td.sandbox)) -} - -// TestBondJoiningCommittee checks if a validator attempts to increase their -// stake after evaluating sortition. -// In non-strict mode, it should be accepted. -func TestBondJoiningCommittee(t *testing.T) { - td := setup(t) - - exe1 := NewBondExecutor(true) - exe2 := NewBondExecutor(false) - senderAddr, _ := td.sandbox.TestStore.RandomTestAcc() - pub, _ := td.RandBLSKeyPair() - amt := td.RandAmountRange( - td.sandbox.TestParams.MinimumStake, - td.sandbox.TestParams.MaximumStake-10e9) // it has 10e9 stake - fee := td.RandFee() - lockTime := td.sandbox.CurrentHeight() - - val := td.sandbox.MakeNewValidator(pub) - val.UpdateLastBondingHeight(1) - val.UpdateLastSortitionHeight(td.sandbox.CurrentHeight()) - td.sandbox.UpdateValidator(val) - td.sandbox.JoinedToCommittee(val.Address()) - - trx := tx.NewBondTx(lockTime, senderAddr, - pub.ValidatorAddress(), nil, amt, fee, "joining committee") - - assert.Error(t, exe1.Execute(trx, td.sandbox)) - assert.NoError(t, exe2.Execute(trx, td.sandbox)) -} - -// TestStakeExceeded checks if the validator's stake exceeded the MaximumStake parameter. -func TestStakeExceeded(t *testing.T) { - td := setup(t) + t.Run("Ok", func(t *testing.T) { + trx := tx.NewBondTx(lockTime, senderAddr, receiverAddr, valPub, amt, fee, "ok") - exe := NewBondExecutor(true) - amt := td.sandbox.TestParams.MaximumStake + 1 - fee := td.RandFee() - senderAddr, senderAcc := td.sandbox.TestStore.RandomTestAcc() - senderAcc.AddToBalance(td.sandbox.TestParams.MaximumStake + 1) - td.sandbox.UpdateAccount(senderAddr, senderAcc) - pub, _ := td.RandBLSKeyPair() - lockTime := td.sandbox.CurrentHeight() + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) + }) - trx := tx.NewBondTx(lockTime, senderAddr, - pub.ValidatorAddress(), pub, amt, fee, "stake exceeded") + updatedSenderAcc := td.sandbox.Account(senderAddr) + updatedReceiverVal := td.sandbox.Validator(receiverAddr) + assert.Equal(t, senderBalance-(amt+fee), updatedSenderAcc.Balance()) + assert.Equal(t, amt, updatedReceiverVal.Stake()) + assert.Equal(t, lockTime, updatedReceiverVal.LastBondingHeight()) - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAmount) + td.checkTotalCoin(t, fee) } func TestPowerDeltaBond(t *testing.T) { td := setup(t) - exe := NewBondExecutor(true) senderAddr, _ := td.sandbox.TestStore.RandomTestAcc() pub, _ := td.RandBLSKeyPair() @@ -192,11 +135,9 @@ func TestPowerDeltaBond(t *testing.T) { td.sandbox.TestParams.MaximumStake) fee := td.RandFee() lockTime := td.sandbox.CurrentHeight() - trx := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, pub, amt, fee, "ok") + trx := tx.NewBondTx(lockTime, senderAddr, receiverAddr, pub, amt, fee, "ok") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err, "Ok") + td.execute(t, trx) assert.Equal(t, int64(amt), td.sandbox.PowerDelta()) } @@ -206,52 +147,49 @@ func TestPowerDeltaBond(t *testing.T) { // https://github.com/pactus-project/pactus/issues/1223 func TestSmallBond(t *testing.T) { td := setup(t) - exe := NewBondExecutor(false) - td.sandbox.TestStore.AddTestBlock(752000 + 1) // TODO: remove me in future senderAddr, _ := td.sandbox.TestStore.RandomTestAcc() - receiverVal := td.sandbox.TestStore.RandomTestVal() - receiverAddr := receiverVal.Address() - fee := td.RandFee() + receiverPub, _ := td.RandBLSKeyPair() + receiverAddr := receiverPub.ValidatorAddress() + receiverVal := td.sandbox.MakeNewValidator(receiverPub) + receiverVal.AddToStake(td.sandbox.TestParams.MaximumStake - 2) + td.sandbox.UpdateValidator(receiverVal) lockTime := td.sandbox.CurrentHeight() - trxBond := tx.NewBondTx(lockTime, senderAddr, - receiverAddr, nil, 1000e9-receiverVal.Stake()-2, fee, "ok") - - err := exe.Execute(trxBond, td.sandbox) - assert.NoError(t, err, "Ok") + fee := td.RandFee() t.Run("Rejects bond transaction with zero amount", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, receiverAddr, nil, 0, fee, "attacking validator") - err := exe.Execute(trx, td.sandbox) - assert.Error(t, err, "Zero bond amount should be rejected") + td.check(t, trx, true, SmallStakeError{td.sandbox.TestParams.MinimumStake}) + td.check(t, trx, false, SmallStakeError{td.sandbox.TestParams.MinimumStake}) }) t.Run("Rejects bond transaction below full validator stake", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, receiverAddr, nil, 1, fee, "attacking validator") - err := exe.Execute(trx, td.sandbox) - assert.Error(t, err, "Bond amount below full stake should be rejected") + td.check(t, trx, true, SmallStakeError{td.sandbox.TestParams.MinimumStake}) + td.check(t, trx, false, SmallStakeError{td.sandbox.TestParams.MinimumStake}) }) t.Run("Accepts bond transaction reaching full validator stake", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, receiverAddr, nil, 2, fee, "fulfilling validator stake") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err, "Bond reaching full stake should be accepted") + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) }) - t.Run("Accepts bond transaction with zero amount on full validator", func(t *testing.T) { + t.Run("Rejects bond transaction with zero amount on full validator", func(t *testing.T) { trx := tx.NewBondTx(lockTime, senderAddr, receiverAddr, nil, 0, fee, "attacking validator") - err := exe.Execute(trx, td.sandbox) - assert.Error(t, err, "Zero bond amount on full stake should be rejected") + td.check(t, trx, true, SmallStakeError{td.sandbox.TestParams.MinimumStake}) + td.check(t, trx, false, SmallStakeError{td.sandbox.TestParams.MinimumStake}) }) - val, _ := td.sandbox.TestStore.Validator(receiverVal.Address()) - assert.Equal(t, td.sandbox.Params().MaximumStake, val.Stake()) + receiverValAfterExecution, _ := td.sandbox.TestStore.Validator(receiverVal.Address()) + assert.Equal(t, td.sandbox.Params().MaximumStake, receiverValAfterExecution.Stake()) } diff --git a/execution/executor/errors.go b/execution/executor/errors.go index 3ed28c988..d67d3f15b 100644 --- a/execution/executor/errors.go +++ b/execution/executor/errors.go @@ -1,6 +1,98 @@ package executor -import "errors" +import ( + "errors" + "fmt" -// ErrInsufficientFunds indicates the balance is low for the transaction. + "github.com/pactus-project/pactus/crypto" + "github.com/pactus-project/pactus/types/amount" + "github.com/pactus-project/pactus/types/tx/payload" +) + +// ErrInsufficientFunds indicates that the balance is insufficient for the transaction. var ErrInsufficientFunds = errors.New("insufficient funds") + +// ErrPublicKeyNotSet indicates that the public key is not set for the initial Bond transaction. +var ErrPublicKeyNotSet = errors.New("public key is not set") + +// ErrPublicKeyAlreadySet indicates that the public key has already been set for the given validator. +var ErrPublicKeyAlreadySet = errors.New("public key is already set") + +// ErrValidatorBonded indicates that the validator is bonded. +var ErrValidatorBonded = errors.New("validator is bonded") + +// ErrValidatorUnbonded indicates that the validator has unbonded. +var ErrValidatorUnbonded = errors.New("validator has unbonded") + +// ErrBondingPeriod is returned when a validator is in the bonding period. +var ErrBondingPeriod = errors.New("validator in bonding period") + +// ErrUnbondingPeriod is returned when a validator is in the unbonding period. +var ErrUnbondingPeriod = errors.New("validator in unbonding period") + +// ErrInvalidSortitionProof indicates that the sortition proof is invalid. +var ErrInvalidSortitionProof = errors.New("invalid sortition proof") + +// ErrExpiredSortition indicates that the sortition transaction is duplicated or expired. +var ErrExpiredSortition = errors.New("expired sortition") + +// ErrValidatorInCommittee indicates that the validator is in the committee. +var ErrValidatorInCommittee = errors.New("validator is in the committee") + +// ErrCommitteeJoinLimitExceeded indicates that at each height, +// the maximum stake joining the committee can't be more than 1/3 of the committee's total stake. +var ErrCommitteeJoinLimitExceeded = errors.New( + "the maximum stake joining the committee can't be more than 1/3 of the committee's total stake") + +// ErrCommitteeLeaveLimitExceeded indicates that at each height, +// the maximum stake leaving the committee can't be more than 1/3 of the committee's total stake. +var ErrCommitteeLeaveLimitExceeded = errors.New( + "the maximum stake leaving the committee can't be more than 1/3 of the committee's total stake") + +// ErrOldestValidatorNotProposed indicates that the oldest validator has not proposed any block yet. +var ErrOldestValidatorNotProposed = errors.New("oldest validator has not proposed any block yet") + +// SmallStakeError is returned when the stake amount is less than the minimum stake. +type SmallStakeError struct { + Minimum amount.Amount +} + +func (e SmallStakeError) Error() string { + return fmt.Sprintf("stake amount can't be less than %v", e.Minimum.String()) +} + +// MaximumStakeError is returned when the validator's stake exceeds the maximum stake limit. +type MaximumStakeError struct { + Maximum amount.Amount +} + +func (e MaximumStakeError) Error() string { + return fmt.Sprintf("validator's stake amount can't be more than %v", e.Maximum.String()) +} + +// InvalidPayloadTypeError is returned when the transaction payload type is not valid. +type InvalidPayloadTypeError struct { + PayloadType payload.Type +} + +func (e InvalidPayloadTypeError) Error() string { + return fmt.Sprintf("unknown payload type: %s", e.PayloadType.String()) +} + +// AccountNotFoundError is raised when the given address has no associated account. +type AccountNotFoundError struct { + Address crypto.Address +} + +func (e AccountNotFoundError) Error() string { + return fmt.Sprintf("no account found for address: %s", e.Address.String()) +} + +// ValidatorNotFoundError is raised when the given address has no associated validator. +type ValidatorNotFoundError struct { + Address crypto.Address +} + +func (e ValidatorNotFoundError) Error() string { + return fmt.Sprintf("no validator found for address: %s", e.Address.String()) +} diff --git a/execution/executor/executor.go b/execution/executor/executor.go new file mode 100644 index 000000000..a608557bb --- /dev/null +++ b/execution/executor/executor.go @@ -0,0 +1,35 @@ +package executor + +import ( + "github.com/pactus-project/pactus/sandbox" + "github.com/pactus-project/pactus/types/tx" + "github.com/pactus-project/pactus/types/tx/payload" +) + +type Executor interface { + Check(strict bool) error + Execute() +} + +func MakeExecutor(trx *tx.Tx, sb sandbox.Sandbox) (Executor, error) { + var exe Executor + var err error + switch t := trx.Payload().Type(); t { + case payload.TypeTransfer: + exe, err = newTransferExecutor(trx, sb) + case payload.TypeBond: + exe, err = newBondExecutor(trx, sb) + case payload.TypeUnbond: + exe, err = newUnbondExecutor(trx, sb) + case payload.TypeWithdraw: + exe, err = newWithdrawExecutor(trx, sb) + case payload.TypeSortition: + exe, err = newSortitionExecutor(trx, sb) + default: + return nil, InvalidPayloadTypeError{ + PayloadType: t, + } + } + + return exe, err +} diff --git a/execution/executor/executor_test.go b/execution/executor/executor_test.go new file mode 100644 index 000000000..82386d9ca --- /dev/null +++ b/execution/executor/executor_test.go @@ -0,0 +1,70 @@ +package executor + +import ( + "testing" + + "github.com/pactus-project/pactus/sandbox" + "github.com/pactus-project/pactus/types/amount" + "github.com/pactus-project/pactus/types/tx" + "github.com/pactus-project/pactus/util/testsuite" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testData struct { + *testsuite.TestSuite + + sandbox *sandbox.MockSandbox +} + +func setup(t *testing.T) *testData { + t.Helper() + + ts := testsuite.NewTestSuite(t) + + sb := sandbox.MockingSandbox(ts) + randHeight := ts.RandHeight() + _ = sb.TestStore.AddTestBlock(randHeight) + + return &testData{ + TestSuite: ts, + sandbox: sb, + } +} + +func (td *testData) checkTotalCoin(t *testing.T, fee amount.Amount) { + t.Helper() + + total := amount.Amount(0) + for _, acc := range td.sandbox.TestStore.Accounts { + total += acc.Balance() + } + + for _, val := range td.sandbox.TestStore.Validators { + total += val.Stake() + } + assert.Equal(t, total+fee, amount.Amount(21_000_000*1e9)) +} + +func (td *testData) check(t *testing.T, trx *tx.Tx, strict bool, expectedErr error) { + t.Helper() + + exe, err := MakeExecutor(trx, td.sandbox) + if err != nil { + assert.ErrorIs(t, err, expectedErr) + + return + } + + err = exe.Check(strict) + assert.ErrorIs(t, err, expectedErr) +} + +func (td *testData) execute(t *testing.T, trx *tx.Tx) { + t.Helper() + + exe, err := MakeExecutor(trx, td.sandbox) + require.NoError(t, err) + + exe.Execute() +} diff --git a/execution/executor/sortition.go b/execution/executor/sortition.go index fc0e6a0f4..dedf6a7b1 100644 --- a/execution/executor/sortition.go +++ b/execution/executor/sortition.go @@ -7,75 +7,72 @@ import ( "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/tx/payload" "github.com/pactus-project/pactus/types/validator" - "github.com/pactus-project/pactus/util/errors" ) type SortitionExecutor struct { - strict bool + sb sandbox.Sandbox + pld *payload.SortitionPayload + validator *validator.Validator + sortitionHeight uint32 } -func NewSortitionExecutor(strict bool) *SortitionExecutor { - return &SortitionExecutor{strict: strict} -} - -func (e *SortitionExecutor) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { +func newSortitionExecutor(trx *tx.Tx, sb sandbox.Sandbox) (*SortitionExecutor, error) { pld := trx.Payload().(*payload.SortitionPayload) val := sb.Validator(pld.Validator) if val == nil { - return errors.Errorf(errors.ErrInvalidAddress, - "unable to retrieve validator") + return nil, ValidatorNotFoundError{ + Address: pld.Validator, + } } - if sb.CurrentHeight()-val.LastBondingHeight() < sb.Params().BondInterval { - return errors.Errorf(errors.ErrInvalidHeight, - "validator has bonded at height %v", val.LastBondingHeight()) + return &SortitionExecutor{ + pld: pld, + sb: sb, + validator: val, + sortitionHeight: trx.LockTime(), + }, nil +} + +func (e *SortitionExecutor) Check(strict bool) error { + if e.sb.CurrentHeight()-e.validator.LastBondingHeight() < e.sb.Params().BondInterval { + return ErrBondingPeriod } - sortitionHeight := trx.LockTime() - ok := sb.VerifyProof(sortitionHeight, pld.Proof, val) + ok := e.sb.VerifyProof(e.sortitionHeight, e.pld.Proof, e.validator) if !ok { - return errors.Error(errors.ErrInvalidProof) + return ErrInvalidSortitionProof } // Check for the duplicated or expired sortition transactions - if sortitionHeight <= val.LastSortitionHeight() { - return errors.Errorf(errors.ErrInvalidTx, - "duplicated sortition transaction") + if e.sortitionHeight <= e.validator.LastSortitionHeight() { + return ErrExpiredSortition } - if e.strict { - if err := e.joinCommittee(sb, val); err != nil { + if strict { + if err := e.canJoinCommittee(); err != nil { return err } } - val.UpdateLastSortitionHeight(sortitionHeight) - - sb.JoinedToCommittee(pld.Validator) - sb.UpdateValidator(val) - return nil } -func (*SortitionExecutor) joinCommittee(sb sandbox.Sandbox, - val *validator.Validator, -) error { - if sb.Committee().Size() < sb.Params().CommitteeSize { +func (e *SortitionExecutor) canJoinCommittee() error { + if e.sb.Committee().Size() < e.sb.Params().CommitteeSize { // There are available seats in the committee. - if sb.Committee().Contains(val.Address()) { - return errors.Errorf(errors.ErrInvalidTx, - "validator is in committee") + if e.sb.Committee().Contains(e.pld.Validator) { + return ErrValidatorInCommittee } return nil } - // The committee is full, check if the validator can enter the committee. + // The committee is full, check if the validator can join the committee. joiningNum := 0 joiningPower := int64(0) - committee := sb.Committee() - sb.IterateValidators(func(val *validator.Validator, _ bool, joined bool) { + committee := e.sb.Committee() + e.sb.IterateValidators(func(val *validator.Validator, _ bool, joined bool) { if joined { if !committee.Contains(val.Address()) { joiningPower += val.Power() @@ -83,13 +80,12 @@ func (*SortitionExecutor) joinCommittee(sb sandbox.Sandbox, } } }) - if !committee.Contains(val.Address()) { - joiningPower += val.Power() + if !committee.Contains(e.pld.Validator) { + joiningPower += e.validator.Power() joiningNum++ } if joiningPower >= (committee.TotalPower() / 3) { - return errors.Errorf(errors.ErrInvalidTx, - "in each height only 1/3 of stake can join") + return ErrCommitteeJoinLimitExceeded } vals := committee.Validators() @@ -101,11 +97,10 @@ func (*SortitionExecutor) joinCommittee(sb sandbox.Sandbox, leavingPower += vals[i].Power() } if leavingPower >= (committee.TotalPower() / 3) { - return errors.Errorf(errors.ErrInvalidTx, - "in each height only 1/3 of stake can leave") + return ErrCommitteeLeaveLimitExceeded } - oldestSortitionHeight := sb.CurrentHeight() + oldestSortitionHeight := e.sb.CurrentHeight() for _, v := range committee.Validators() { if v.LastSortitionHeight() < oldestSortitionHeight { oldestSortitionHeight = v.LastSortitionHeight() @@ -113,12 +108,18 @@ func (*SortitionExecutor) joinCommittee(sb sandbox.Sandbox, } // If the oldest validator in the committee still hasn't propose a block yet, - // she stays in the committee. - proposerHeight := sb.Committee().Proposer(0).LastSortitionHeight() + // it stays in the committee. + proposerHeight := e.sb.Committee().Proposer(0).LastSortitionHeight() if oldestSortitionHeight >= proposerHeight { - return errors.Errorf(errors.ErrInvalidTx, - "oldest validator still didn't propose any block") + return ErrOldestValidatorNotProposed } return nil } + +func (e *SortitionExecutor) Execute() { + e.validator.UpdateLastSortitionHeight(e.sortitionHeight) + + e.sb.JoinedToCommittee(e.pld.Validator) + e.sb.UpdateValidator(e.validator) +} diff --git a/execution/executor/sortition_test.go b/execution/executor/sortition_test.go index c96d8f501..bd2c75fd5 100644 --- a/execution/executor/sortition_test.go +++ b/execution/executor/sortition_test.go @@ -7,7 +7,6 @@ import ( "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/validator" - "github.com/pactus-project/pactus/util/errors" "github.com/stretchr/testify/assert" ) @@ -25,103 +24,104 @@ func updateCommittee(td *testData) { func TestExecuteSortitionTx(t *testing.T) { td := setup(t) - exe := NewSortitionExecutor(true) - - existingVal := td.sandbox.TestStore.RandomTestVal() - pub, _ := td.RandBLSKeyPair() - newVal := td.sandbox.MakeNewValidator(pub) - accAddr, acc := td.sandbox.TestStore.RandomTestAcc() - amt := td.RandAmountRange(0, acc.Balance()) - fee := td.RandFee() - newVal.AddToStake(amt + fee) - acc.SubtractFromBalance(amt + fee) - td.sandbox.UpdateAccount(accAddr, acc) - td.sandbox.UpdateValidator(newVal) + + bonderAddr, bonderAcc := td.sandbox.TestStore.RandomTestAcc() + bonderBalance := bonderAcc.Balance() + stake := td.RandAmountRange( + td.sandbox.TestParams.MinimumStake, + bonderBalance) + bonderAcc.SubtractFromBalance(stake) + td.sandbox.UpdateAccount(bonderAddr, bonderAcc) + + valPub, _ := td.RandBLSKeyPair() + valAddr := valPub.ValidatorAddress() + val := td.sandbox.MakeNewValidator(valPub) + val.AddToStake(stake) + td.sandbox.UpdateValidator(val) + curHeight := td.sandbox.CurrentHeight() - lockTime := curHeight + lockTime := td.sandbox.CurrentHeight() proof := td.RandProof() - newVal.UpdateLastBondingHeight(curHeight - td.sandbox.Params().BondInterval) - td.sandbox.UpdateValidator(newVal) - assert.Zero(t, td.sandbox.Validator(newVal.Address()).LastSortitionHeight()) - assert.False(t, td.sandbox.IsJoinedCommittee(newVal.Address())) + val.UpdateLastBondingHeight(curHeight - td.sandbox.Params().BondInterval) + td.sandbox.UpdateValidator(val) - t.Run("Should fail, Invalid address", func(t *testing.T) { - trx := tx.NewSortitionTx(lockTime, td.RandAccAddress(), proof) - td.sandbox.TestAcceptSortition = true - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) + assert.Zero(t, val.LastSortitionHeight()) + assert.False(t, td.sandbox.IsJoinedCommittee(val.Address())) + + t.Run("Should fail, unknown address", func(t *testing.T) { + randomAddr := td.RandAccAddress() + trx := tx.NewSortitionTx(lockTime, randomAddr, proof) + + td.check(t, trx, true, ValidatorNotFoundError{Address: randomAddr}) + td.check(t, trx, false, ValidatorNotFoundError{Address: randomAddr}) }) - newVal.UpdateLastBondingHeight(curHeight - td.sandbox.Params().BondInterval + 1) - td.sandbox.UpdateValidator(newVal) + val.UpdateLastBondingHeight(curHeight - td.sandbox.Params().BondInterval + 1) + td.sandbox.UpdateValidator(val) t.Run("Should fail, Bonding period", func(t *testing.T) { - trx := tx.NewSortitionTx(lockTime, newVal.Address(), proof) - td.sandbox.TestAcceptSortition = true - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidHeight) + trx := tx.NewSortitionTx(lockTime, val.Address(), proof) + + td.check(t, trx, true, ErrBondingPeriod) + td.check(t, trx, false, ErrBondingPeriod) }) // Let's add one more block td.sandbox.TestStore.AddTestBlock(curHeight + 1) - t.Run("Should fail, Invalid proof", func(t *testing.T) { - trx := tx.NewSortitionTx(lockTime, newVal.Address(), proof) + t.Run("Should fail, invalid proof", func(t *testing.T) { + trx := tx.NewSortitionTx(lockTime, val.Address(), proof) td.sandbox.TestAcceptSortition = false - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidProof) + + td.check(t, trx, true, ErrInvalidSortitionProof) + td.check(t, trx, false, ErrInvalidSortitionProof) }) - t.Run("Should fail, Committee has free seats and validator is in the committee", func(t *testing.T) { - trx := tx.NewSortitionTx(lockTime, existingVal.Address(), proof) + t.Run("Should fail, committee has free seats and validator is in the committee", func(t *testing.T) { + val0 := td.sandbox.Committee().Proposer(0) + trx := tx.NewSortitionTx(lockTime, val0.Address(), proof) td.sandbox.TestAcceptSortition = true - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) + + td.check(t, trx, true, ErrValidatorInCommittee) + td.check(t, trx, false, nil) }) t.Run("Should be ok", func(t *testing.T) { - trx := tx.NewSortitionTx(lockTime, newVal.Address(), proof) + trx := tx.NewSortitionTx(lockTime, val.Address(), proof) td.sandbox.TestAcceptSortition = true - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err) + + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) }) - t.Run("Should fail, duplicated sortition", func(t *testing.T) { - trx := tx.NewSortitionTx(lockTime, newVal.Address(), proof) + t.Run("Should fail, expired sortition", func(t *testing.T) { + trx := tx.NewSortitionTx(lockTime-1, val.Address(), proof) td.sandbox.TestAcceptSortition = true - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) + + td.check(t, trx, true, ErrExpiredSortition) + td.check(t, trx, false, ErrExpiredSortition) }) - assert.Equal(t, td.sandbox.Validator(newVal.Address()).LastSortitionHeight(), lockTime) - assert.True(t, td.sandbox.IsJoinedCommittee(newVal.Address())) + t.Run("Should fail, duplicated sortition", func(t *testing.T) { + trx := tx.NewSortitionTx(lockTime, val.Address(), proof) - td.checkTotalCoin(t, 0) -} + td.check(t, trx, true, ErrExpiredSortition) + td.check(t, trx, false, ErrExpiredSortition) + }) -func TestSortitionNonStrictMode(t *testing.T) { - td := setup(t) - exe1 := NewSortitionExecutor(true) - exe2 := NewSortitionExecutor(false) + updatedVal := td.sandbox.Validator(valAddr) - val := td.sandbox.TestStore.RandomTestVal() - lockTime := td.sandbox.CurrentHeight() - proof := td.RandProof() + assert.Equal(t, lockTime, updatedVal.LastSortitionHeight()) + assert.True(t, td.sandbox.IsJoinedCommittee(val.Address())) - td.sandbox.TestAcceptSortition = true - trx := tx.NewSortitionTx(lockTime, val.Address(), proof) - err := exe1.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) - err = exe2.Execute(trx, td.sandbox) - assert.NoError(t, err) + td.checkTotalCoin(t, 0) } func TestChangePower1(t *testing.T) { td := setup(t) - exe := NewSortitionExecutor(true) - // This moves proposer to next validator updateCommittee(td) @@ -141,30 +141,30 @@ func TestChangePower1(t *testing.T) { td.sandbox.UpdateValidator(val2) lockTime := td.sandbox.CurrentHeight() proof2 := td.RandProof() - val3 := td.sandbox.Committee().Validators()[0] + + val3 := td.sandbox.Committee().Proposer(0) proof3 := td.RandProof() td.sandbox.TestParams.CommitteeSize = 4 td.sandbox.TestAcceptSortition = true trx1 := tx.NewSortitionTx(lockTime, val1.Address(), proof1) - err := exe.Execute(trx1, td.sandbox) - assert.NoError(t, err) + td.check(t, trx1, true, nil) + td.check(t, trx1, false, nil) + td.execute(t, trx1) trx2 := tx.NewSortitionTx(lockTime, val2.Address(), proof2) - err = exe.Execute(trx2, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx, "More than 1/3 of power is joining at the same height") + td.check(t, trx2, true, ErrCommitteeJoinLimitExceeded) + td.check(t, trx2, false, nil) // Val3 is a Committee member trx3 := tx.NewSortitionTx(lockTime, val3.Address(), proof3) - err = exe.Execute(trx3, td.sandbox) - assert.NoError(t, err) + td.check(t, trx3, true, nil) + td.check(t, trx3, false, nil) } func TestChangePower2(t *testing.T) { td := setup(t) - exe := NewSortitionExecutor(true) - // This moves proposer to next validator updateCommittee(td) @@ -190,27 +190,31 @@ func TestChangePower2(t *testing.T) { td.sandbox.UpdateValidator(val3) lockTime := td.sandbox.CurrentHeight() proof3 := td.RandProof() - val4 := td.sandbox.Committee().Validators()[0] + + val4 := td.sandbox.Committee().Proposer(0) proof4 := td.RandProof() td.sandbox.TestParams.CommitteeSize = 7 td.sandbox.TestAcceptSortition = true trx1 := tx.NewSortitionTx(lockTime, val1.Address(), proof1) - err := exe.Execute(trx1, td.sandbox) - assert.NoError(t, err) + td.check(t, trx1, true, nil) + td.check(t, trx1, false, nil) + td.execute(t, trx1) trx2 := tx.NewSortitionTx(lockTime, val2.Address(), proof2) - err = exe.Execute(trx2, td.sandbox) - assert.NoError(t, err) + td.check(t, trx2, true, nil) + td.check(t, trx2, false, nil) + td.execute(t, trx2) trx3 := tx.NewSortitionTx(lockTime, val3.Address(), proof3) - err = exe.Execute(trx3, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx, "More than 1/3 of power is leaving at the same height") + td.check(t, trx3, true, ErrCommitteeLeaveLimitExceeded) + td.check(t, trx3, false, nil) // Committee member trx4 := tx.NewSortitionTx(lockTime, val4.Address(), proof4) - err = exe.Execute(trx4, td.sandbox) - assert.NoError(t, err) + td.check(t, trx4, true, nil) + td.check(t, trx4, false, nil) + td.execute(t, trx4) } // TestOldestDidNotPropose tests if the oldest validator in the committee had @@ -218,8 +222,6 @@ func TestChangePower2(t *testing.T) { func TestOldestDidNotPropose(t *testing.T) { td := setup(t) - exe := NewSortitionExecutor(true) - // Let's create validators first vals := make([]*validator.Validator, 9) for i := 0; i < 9; i++ { @@ -245,10 +247,11 @@ func TestOldestDidNotPropose(t *testing.T) { _ = td.sandbox.TestStore.AddTestBlock(height) lockTime := height - trx1 := tx.NewSortitionTx(lockTime, - vals[i].Address(), td.RandProof()) - err := exe.Execute(trx1, td.sandbox) - assert.NoError(t, err) + trx := tx.NewSortitionTx(lockTime, vals[i].Address(), td.RandProof()) + + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) updateCommittee(td) } @@ -258,18 +261,22 @@ func TestOldestDidNotPropose(t *testing.T) { lockTime := td.sandbox.CurrentHeight() trx1 := tx.NewSortitionTx(lockTime, vals[7].Address(), td.RandProof()) - err := exe.Execute(trx1, td.sandbox) - assert.NoError(t, err) + td.check(t, trx1, true, nil) + td.check(t, trx1, false, nil) + td.execute(t, trx1) trx2 := tx.NewSortitionTx(lockTime, vals[8].Address(), td.RandProof()) - err = exe.Execute(trx2, td.sandbox) - assert.NoError(t, err) + td.check(t, trx2, true, nil) + td.check(t, trx2, false, nil) + td.execute(t, trx2) + updateCommittee(td) height++ _ = td.sandbox.TestStore.AddTestBlock(height) // Entering validator 16 - trx3 := tx.NewSortitionTx(lockTime, vals[8].Address(), td.RandProof()) - err = exe.Execute(trx3, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) + trx3 := tx.NewSortitionTx(lockTime+1, vals[8].Address(), td.RandProof()) + td.check(t, trx3, true, ErrOldestValidatorNotProposed) + td.check(t, trx3, false, nil) + td.execute(t, trx3) } diff --git a/execution/executor/transfer.go b/execution/executor/transfer.go index 1b84d0b66..fc733f7f1 100644 --- a/execution/executor/transfer.go +++ b/execution/executor/transfer.go @@ -3,46 +3,58 @@ package executor import ( "github.com/pactus-project/pactus/sandbox" "github.com/pactus-project/pactus/types/account" + "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/tx/payload" - "github.com/pactus-project/pactus/util/errors" ) type TransferExecutor struct { - strict bool + sb sandbox.Sandbox + pld *payload.TransferPayload + fee amount.Amount + sender *account.Account + receiver *account.Account } -func NewTransferExecutor(strict bool) *TransferExecutor { - return &TransferExecutor{strict: strict} -} - -func (*TransferExecutor) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { +func newTransferExecutor(trx *tx.Tx, sb sandbox.Sandbox) (*TransferExecutor, error) { pld := trx.Payload().(*payload.TransferPayload) - senderAcc := sb.Account(pld.From) - if senderAcc == nil { - return errors.Errorf(errors.ErrInvalidAddress, - "unable to retrieve sender account") + sender := sb.Account(pld.From) + if sender == nil { + return nil, AccountNotFoundError{Address: pld.From} } - var receiverAcc *account.Account + + var receiver *account.Account if pld.To == pld.From { - receiverAcc = senderAcc + receiver = sender } else { - receiverAcc = sb.Account(pld.To) - if receiverAcc == nil { - receiverAcc = sb.MakeNewAccount(pld.To) + receiver = sb.Account(pld.To) + if receiver == nil { + receiver = sb.MakeNewAccount(pld.To) } } - if senderAcc.Balance() < pld.Amount+trx.Fee() { + return &TransferExecutor{ + sb: sb, + pld: pld, + fee: trx.Fee(), + sender: sender, + receiver: receiver, + }, nil +} + +func (e *TransferExecutor) Check(_ bool) error { + if e.sender.Balance() < e.pld.Amount+e.fee { return ErrInsufficientFunds } - senderAcc.SubtractFromBalance(pld.Amount + trx.Fee()) - receiverAcc.AddToBalance(pld.Amount) + return nil +} - sb.UpdateAccount(pld.From, senderAcc) - sb.UpdateAccount(pld.To, receiverAcc) +func (e *TransferExecutor) Execute() { + e.sender.SubtractFromBalance(e.pld.Amount + e.fee) + e.receiver.AddToBalance(e.pld.Amount) - return nil + e.sb.UpdateAccount(e.pld.From, e.sender) + e.sb.UpdateAccount(e.pld.To, e.receiver) } diff --git a/execution/executor/transfer_test.go b/execution/executor/transfer_test.go index 2ec5bda96..1cf6f7093 100644 --- a/execution/executor/transfer_test.go +++ b/execution/executor/transfer_test.go @@ -3,93 +3,58 @@ package executor import ( "testing" - "github.com/pactus-project/pactus/sandbox" - "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/util/errors" - "github.com/pactus-project/pactus/util/testsuite" "github.com/stretchr/testify/assert" ) -type testData struct { - *testsuite.TestSuite - - sandbox *sandbox.MockSandbox -} - -func setup(t *testing.T) *testData { - t.Helper() - - ts := testsuite.NewTestSuite(t) - - sb := sandbox.MockingSandbox(ts) - randHeight := ts.RandHeight() - _ = sb.TestStore.AddTestBlock(randHeight) - - return &testData{ - TestSuite: ts, - sandbox: sb, - } -} - -func (td *testData) checkTotalCoin(t *testing.T, fee amount.Amount) { - t.Helper() - - total := amount.Amount(0) - for _, acc := range td.sandbox.TestStore.Accounts { - total += acc.Balance() - } - - for _, val := range td.sandbox.TestStore.Validators { - total += val.Stake() - } - assert.Equal(t, total+fee, amount.Amount(21_000_000*1e9)) -} - func TestExecuteTransferTx(t *testing.T) { td := setup(t) - exe := NewTransferExecutor(true) senderAddr, senderAcc := td.sandbox.TestStore.RandomTestAcc() senderBalance := senderAcc.Balance() receiverAddr := td.RandAccAddress() + amt := td.RandAmountRange(0, senderBalance) fee := td.RandFee() lockTime := td.sandbox.CurrentHeight() - t.Run("Should fail, Sender has no account", func(t *testing.T) { - trx := tx.NewTransferTx(lockTime, td.RandAccAddress(), - receiverAddr, amt, fee, "non-existing account") + t.Run("Should fail, unknown address", func(t *testing.T) { + randomAddr := td.RandAccAddress() + trx := tx.NewTransferTx(lockTime, randomAddr, + receiverAddr, amt, fee, "unknown address") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) + td.check(t, trx, true, AccountNotFoundError{Address: randomAddr}) + td.check(t, trx, false, AccountNotFoundError{Address: randomAddr}) }) t.Run("Should fail, insufficient balance", func(t *testing.T) { trx := tx.NewTransferTx(lockTime, senderAddr, receiverAddr, senderBalance+1, 0, "insufficient balance") - err := exe.Execute(trx, td.sandbox) - assert.ErrorIs(t, err, ErrInsufficientFunds) + td.check(t, trx, true, ErrInsufficientFunds) + td.check(t, trx, false, ErrInsufficientFunds) }) t.Run("Ok", func(t *testing.T) { trx := tx.NewTransferTx(lockTime, senderAddr, receiverAddr, amt, fee, "ok") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err) + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) }) - assert.Equal(t, td.sandbox.Account(senderAddr).Balance(), senderBalance-(amt+fee)) - assert.Equal(t, td.sandbox.Account(receiverAddr).Balance(), amt) + updatedSenderAcc := td.sandbox.Account(senderAddr) + updatedReceiverAcc := td.sandbox.Account(receiverAddr) + + assert.Equal(t, senderBalance-(amt+fee), updatedSenderAcc.Balance()) + assert.Equal(t, amt, updatedReceiverAcc.Balance()) td.checkTotalCoin(t, fee) } func TestTransferToSelf(t *testing.T) { td := setup(t) - exe := NewTransferExecutor(true) senderAddr, senderAcc := td.sandbox.TestStore.RandomTestAcc() amt := td.RandAmountRange(0, senderAcc.Balance()) @@ -97,9 +62,13 @@ func TestTransferToSelf(t *testing.T) { lockTime := td.sandbox.CurrentHeight() trx := tx.NewTransferTx(lockTime, senderAddr, senderAddr, amt, fee, "ok") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err) + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) expectedBalance := senderAcc.Balance() - fee // Fee should be deducted - assert.Equal(t, expectedBalance, td.sandbox.Account(senderAddr).Balance()) + updatedAcc := td.sandbox.Account(senderAddr) + assert.Equal(t, expectedBalance, updatedAcc.Balance()) + + td.checkTotalCoin(t, fee) } diff --git a/execution/executor/unbond.go b/execution/executor/unbond.go index f13702e1d..ed6f6b8cb 100644 --- a/execution/executor/unbond.go +++ b/execution/executor/unbond.go @@ -4,58 +4,62 @@ import ( "github.com/pactus-project/pactus/sandbox" "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/tx/payload" - "github.com/pactus-project/pactus/util/errors" + "github.com/pactus-project/pactus/types/validator" ) type UnbondExecutor struct { - strict bool + sb sandbox.Sandbox + pld *payload.UnbondPayload + validator *validator.Validator } -func NewUnbondExecutor(strict bool) *UnbondExecutor { - return &UnbondExecutor{strict: strict} -} - -func (e *UnbondExecutor) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { +func newUnbondExecutor(trx *tx.Tx, sb sandbox.Sandbox) (*UnbondExecutor, error) { pld := trx.Payload().(*payload.UnbondPayload) val := sb.Validator(pld.Signer()) if val == nil { - return errors.Errorf(errors.ErrInvalidAddress, - "unable to retrieve validator") + return nil, ValidatorNotFoundError{Address: pld.Validator} } - if val.UnbondingHeight() > 0 { - return errors.Errorf(errors.ErrInvalidHeight, - "validator has unbonded at height %v", val.UnbondingHeight()) + return &UnbondExecutor{ + sb: sb, + pld: pld, + validator: val, + }, nil +} + +func (e *UnbondExecutor) Check(strict bool) error { + if e.validator.UnbondingHeight() > 0 { + return ErrValidatorUnbonded } - if e.strict { + + if strict { // In strict mode, the unbond transaction will be rejected if the // validator is in the committee. // In non-strict mode, they are added to the transaction pool and // processed once eligible. - if sb.Committee().Contains(pld.Validator) { - return errors.Errorf(errors.ErrInvalidTx, - "validator %v is in committee", pld.Validator) + if e.sb.Committee().Contains(e.pld.Validator) { + return ErrValidatorInCommittee } // In strict mode, unbond transactions will be rejected if a validator is // going to be in the committee for the next height. // In non-strict mode, they are added to the transaction pool and // processed once eligible. - if sb.IsJoinedCommittee(pld.Validator) { - return errors.Errorf(errors.ErrInvalidHeight, - "validator %v joins committee in the next height", pld.Validator) + if e.sb.IsJoinedCommittee(e.pld.Validator) { + return ErrValidatorInCommittee } } - unbondedPower := val.Power() - val.UpdateUnbondingHeight(sb.CurrentHeight()) + return nil +} - // At this point, the validator's power is zero. - // However, we know the validator's stake. - // So, we can update the power delta with the negative of the validator's stake. - sb.UpdatePowerDelta(-1 * unbondedPower) - sb.UpdateValidator(val) +func (e *UnbondExecutor) Execute() { + unbondedPower := e.validator.Power() + e.validator.UpdateUnbondingHeight(e.sb.CurrentHeight()) - return nil + // The validator's power is reduced to zero, + // so we update the power delta with the negative value of the validator's power. + e.sb.UpdatePowerDelta(-1 * unbondedPower) + e.sb.UpdateValidator(e.validator) } diff --git a/execution/executor/unbond_test.go b/execution/executor/unbond_test.go index b69efdb83..d01c3d4df 100644 --- a/execution/executor/unbond_test.go +++ b/execution/executor/unbond_test.go @@ -4,13 +4,11 @@ import ( "testing" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/util/errors" "github.com/stretchr/testify/assert" ) func TestExecuteUnbondTx(t *testing.T) { td := setup(t) - exe := NewUnbondExecutor(true) bonderAddr, bonderAcc := td.sandbox.TestStore.RandomTestAcc() bonderBalance := bonderAcc.Balance() @@ -27,90 +25,60 @@ func TestExecuteUnbondTx(t *testing.T) { td.sandbox.UpdateValidator(val) lockTime := td.sandbox.CurrentHeight() - t.Run("Should fail, Invalid validator", func(t *testing.T) { - trx := tx.NewUnbondTx(lockTime, td.RandAccAddress(), "invalid validator") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) + t.Run("Should fail, unknown address", func(t *testing.T) { + randomAddr := td.RandValAddress() + trx := tx.NewUnbondTx(lockTime, randomAddr, "unknown address") + + td.check(t, trx, true, ValidatorNotFoundError{Address: randomAddr}) + td.check(t, trx, false, ValidatorNotFoundError{Address: randomAddr}) }) - t.Run("Should fail, Inside committee", func(t *testing.T) { + t.Run("Should fail, inside committee", func(t *testing.T) { val0 := td.sandbox.Committee().Proposer(0) trx := tx.NewUnbondTx(lockTime, val0.Address(), "inside committee") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) + + td.check(t, trx, true, ErrValidatorInCommittee) + td.check(t, trx, false, nil) }) - t.Run("Should fail, Cannot unbond if unbonded already", func(t *testing.T) { - pub, _ := td.RandBLSKeyPair() - unbondedVal := td.sandbox.MakeNewValidator(pub) - unbondedVal.UpdateUnbondingHeight(td.sandbox.CurrentHeight()) - td.sandbox.UpdateValidator(unbondedVal) + t.Run("Should fail, joining committee", func(t *testing.T) { + randPub, _ := td.RandBLSKeyPair() + randVal := td.sandbox.MakeNewValidator(randPub) + td.sandbox.UpdateValidator(randVal) + td.sandbox.JoinedToCommittee(randVal.Address()) + trx := tx.NewUnbondTx(lockTime, randPub.ValidatorAddress(), "joining committee") - trx := tx.NewUnbondTx(lockTime, pub.ValidatorAddress(), "Ok") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidHeight) + td.check(t, trx, true, ErrValidatorInCommittee) + td.check(t, trx, false, nil) }) t.Run("Ok", func(t *testing.T) { trx := tx.NewUnbondTx(lockTime, valAddr, "Ok") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err) - - // Execute again, should fail - err = exe.Execute(trx, td.sandbox) - assert.Error(t, err) + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) }) - assert.Equal(t, stake, td.sandbox.Validator(valAddr).Stake()) - assert.Zero(t, td.sandbox.Validator(valAddr).Power()) - assert.Equal(t, td.sandbox.Validator(valAddr).UnbondingHeight(), td.sandbox.CurrentHeight()) - assert.Equal(t, int64(-val.Stake()), td.sandbox.PowerDelta()) - - td.checkTotalCoin(t, 0) -} - -// TestUnbondInsideCommittee checks if a validator inside the committee tries to -// unbond the stake. -// In non-strict mode it should be accepted. -func TestUnbondInsideCommittee(t *testing.T) { - td := setup(t) - - exe1 := NewUnbondExecutor(true) - exe2 := NewUnbondExecutor(false) - lockTime := td.sandbox.CurrentHeight() - - val := td.sandbox.Committee().Proposer(0) - trx := tx.NewUnbondTx(lockTime, val.Address(), "") + t.Run("Should fail, Cannot unbond if already unbonded", func(t *testing.T) { + trx := tx.NewUnbondTx(lockTime, valAddr, "Ok") - assert.Error(t, exe1.Execute(trx, td.sandbox)) - assert.NoError(t, exe2.Execute(trx, td.sandbox)) -} + td.check(t, trx, true, ErrValidatorUnbonded) + td.check(t, trx, false, ErrValidatorUnbonded) + }) -// TestUnbondJoiningCommittee checks if a validator tries to unbond after -// evaluating sortition. -// In non-strict mode it should be accepted. -func TestUnbondJoiningCommittee(t *testing.T) { - td := setup(t) - exe1 := NewUnbondExecutor(true) - exe2 := NewUnbondExecutor(false) - pub, _ := td.RandBLSKeyPair() + updatedVal := td.sandbox.Validator(valAddr) - curHeight := td.sandbox.CurrentHeight() - val := td.sandbox.MakeNewValidator(pub) - val.UpdateLastSortitionHeight(curHeight) - td.sandbox.UpdateValidator(val) - td.sandbox.JoinedToCommittee(val.Address()) - lockTime := td.sandbox.CurrentHeight() + assert.Equal(t, stake, updatedVal.Stake()) + assert.Zero(t, updatedVal.Power()) + assert.Equal(t, lockTime, updatedVal.UnbondingHeight()) + assert.Equal(t, int64(-stake), td.sandbox.PowerDelta()) - trx := tx.NewUnbondTx(lockTime, pub.ValidatorAddress(), "Ok") - assert.Error(t, exe1.Execute(trx, td.sandbox)) - assert.NoError(t, exe2.Execute(trx, td.sandbox)) + td.checkTotalCoin(t, 0) } func TestPowerDeltaUnbond(t *testing.T) { td := setup(t) - exe := NewUnbondExecutor(true) pub, _ := td.RandBLSKeyPair() valAddr := pub.ValidatorAddress() @@ -121,8 +89,7 @@ func TestPowerDeltaUnbond(t *testing.T) { lockTime := td.sandbox.CurrentHeight() trx := tx.NewUnbondTx(lockTime, valAddr, "Ok") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err) + td.execute(t, trx) assert.Equal(t, int64(-amt), td.sandbox.PowerDelta()) } diff --git a/execution/executor/withdraw.go b/execution/executor/withdraw.go index 3c888928c..eb3397d2d 100644 --- a/execution/executor/withdraw.go +++ b/execution/executor/withdraw.go @@ -2,51 +2,63 @@ package executor import ( "github.com/pactus-project/pactus/sandbox" + "github.com/pactus-project/pactus/types/account" + "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/tx/payload" - "github.com/pactus-project/pactus/util/errors" + "github.com/pactus-project/pactus/types/validator" ) type WithdrawExecutor struct { - strict bool + sb sandbox.Sandbox + pld *payload.WithdrawPayload + fee amount.Amount + sender *validator.Validator + receiver *account.Account } -func NewWithdrawExecutor(strict bool) *WithdrawExecutor { - return &WithdrawExecutor{strict: strict} -} - -func (*WithdrawExecutor) Execute(trx *tx.Tx, sb sandbox.Sandbox) error { +func newWithdrawExecutor(trx *tx.Tx, sb sandbox.Sandbox) (*WithdrawExecutor, error) { pld := trx.Payload().(*payload.WithdrawPayload) - val := sb.Validator(pld.From) - if val == nil { - return errors.Errorf(errors.ErrInvalidAddress, - "unable to retrieve validator account") + sender := sb.Validator(pld.From) + if sender == nil { + return nil, ValidatorNotFoundError{Address: pld.From} } - if val.Stake() < pld.Amount+trx.Fee() { - return ErrInsufficientFunds + receiver := sb.Account(pld.To) + if receiver == nil { + receiver = sb.MakeNewAccount(pld.To) } - if val.UnbondingHeight() == 0 { - return errors.Errorf(errors.ErrInvalidHeight, - "need to unbond first") + + return &WithdrawExecutor{ + sb: sb, + pld: pld, + fee: trx.Fee(), + sender: sender, + receiver: receiver, + }, nil +} + +func (e *WithdrawExecutor) Check(_ bool) error { + if e.sender.Stake() < e.pld.Amount+e.fee { + return ErrInsufficientFunds } - if sb.CurrentHeight() < val.UnbondingHeight()+sb.Params().UnbondInterval { - return errors.Errorf(errors.ErrInvalidHeight, - "hasn't passed unbonding period, expected: %v, got: %v", - val.UnbondingHeight()+sb.Params().UnbondInterval, sb.CurrentHeight()) + + if e.sender.UnbondingHeight() == 0 { + return ErrValidatorBonded } - acc := sb.Account(pld.To) - if acc == nil { - acc = sb.MakeNewAccount(pld.To) + if e.sb.CurrentHeight() < e.sender.UnbondingHeight()+e.sb.Params().UnbondInterval { + return ErrUnbondingPeriod } - val.SubtractFromStake(pld.Amount + trx.Fee()) - acc.AddToBalance(pld.Amount) + return nil +} - sb.UpdateValidator(val) - sb.UpdateAccount(pld.To, acc) +func (e *WithdrawExecutor) Execute() { + e.sender.SubtractFromStake(e.pld.Amount + e.fee) + e.receiver.AddToBalance(e.pld.Amount) - return nil + e.sb.UpdateValidator(e.sender) + e.sb.UpdateAccount(e.pld.To, e.receiver) } diff --git a/execution/executor/withdraw_test.go b/execution/executor/withdraw_test.go index b92cc3ce2..f9498e005 100644 --- a/execution/executor/withdraw_test.go +++ b/execution/executor/withdraw_test.go @@ -4,86 +4,85 @@ import ( "testing" "github.com/pactus-project/pactus/types/tx" - "github.com/pactus-project/pactus/util/errors" "github.com/stretchr/testify/assert" ) func TestExecuteWithdrawTx(t *testing.T) { td := setup(t) - exe := NewWithdrawExecutor(true) - addr := td.RandAccAddress() - pub, _ := td.RandBLSKeyPair() - val := td.sandbox.MakeNewValidator(pub) - accAddr, acc := td.sandbox.TestStore.RandomTestAcc() - amt := td.RandAmountRange(0, acc.Balance()) - fee := td.RandFee() - val.AddToStake(amt + fee) - acc.SubtractFromBalance(amt + fee) - td.sandbox.UpdateAccount(accAddr, acc) + bonderAddr, bonderAcc := td.sandbox.TestStore.RandomTestAcc() + bonderBalance := bonderAcc.Balance() + stake := td.RandAmountRange( + td.sandbox.TestParams.MinimumStake, + bonderBalance) + bonderAcc.SubtractFromBalance(stake) + td.sandbox.UpdateAccount(bonderAddr, bonderAcc) + + valPub, _ := td.RandBLSKeyPair() + val := td.sandbox.MakeNewValidator(valPub) + val.AddToStake(stake) td.sandbox.UpdateValidator(val) - lockTime := td.sandbox.CurrentHeight() - t.Run("Should fail, Invalid validator", func(t *testing.T) { - trx := tx.NewWithdrawTx(lockTime, td.RandAccAddress(), addr, - amt, fee, "invalid validator") - - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidAddress) - }) + totalStake := val.Stake() + fee := td.RandFee() + amt := td.RandAmountRange(0, totalStake-fee) + senderAddr := val.Address() + receiverAddr := td.RandAccAddress() + lockTime := td.sandbox.CurrentHeight() - t.Run("Should fail, insufficient balance", func(t *testing.T) { - trx := tx.NewWithdrawTx(lockTime, val.Address(), addr, - amt+1, fee, "insufficient balance") + t.Run("Should fail, unknown address", func(t *testing.T) { + randomAddr := td.RandValAddress() + trx := tx.NewWithdrawTx(lockTime, randomAddr, receiverAddr, + amt, fee, "unknown address") - err := exe.Execute(trx, td.sandbox) - assert.ErrorIs(t, err, ErrInsufficientFunds) + td.check(t, trx, true, ValidatorNotFoundError{Address: randomAddr}) + td.check(t, trx, false, ValidatorNotFoundError{Address: randomAddr}) }) t.Run("Should fail, hasn't unbonded yet", func(t *testing.T) { - assert.Zero(t, val.UnbondingHeight()) - trx := tx.NewWithdrawTx(lockTime, val.Address(), addr, + trx := tx.NewWithdrawTx(lockTime, senderAddr, receiverAddr, amt, fee, "need to unbond first") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidHeight) + td.check(t, trx, true, ErrValidatorBonded) + td.check(t, trx, false, ErrValidatorBonded) }) val.UpdateUnbondingHeight(td.sandbox.CurrentHeight() - td.sandbox.Params().UnbondInterval + 1) td.sandbox.UpdateValidator(val) - t.Run("Should fail, hasn't passed unbonding interval", func(t *testing.T) { - assert.NotZero(t, val.UnbondingHeight()) - trx := tx.NewWithdrawTx(lockTime, val.Address(), addr, - amt, fee, "not passed unbonding interval") - err := exe.Execute(trx, td.sandbox) - assert.Equal(t, errors.Code(err), errors.ErrInvalidHeight) + t.Run("Should fail, insufficient balance", func(t *testing.T) { + trx := tx.NewWithdrawTx(lockTime, senderAddr, receiverAddr, + totalStake, 1, "insufficient balance") + + td.check(t, trx, true, ErrInsufficientFunds) + td.check(t, trx, false, ErrInsufficientFunds) + }) + + t.Run("Should fail, hasn't passed unbonding period", func(t *testing.T) { + trx := tx.NewWithdrawTx(lockTime, senderAddr, receiverAddr, + amt, fee, "unbonding period") + + td.check(t, trx, true, ErrUnbondingPeriod) + td.check(t, trx, false, ErrUnbondingPeriod) }) curHeight := td.sandbox.CurrentHeight() td.sandbox.TestStore.AddTestBlock(curHeight + 1) t.Run("Should pass, Everything is Ok!", func(t *testing.T) { - trx := tx.NewWithdrawTx(lockTime, val.Address(), addr, - amt, fee, "should be able to empty stake") + trx := tx.NewWithdrawTx(lockTime, senderAddr, receiverAddr, + amt, fee, "should be able to withdraw the stake") - err := exe.Execute(trx, td.sandbox) - assert.NoError(t, err) + td.check(t, trx, true, nil) + td.check(t, trx, false, nil) + td.execute(t, trx) }) - t.Run("Should fail, can't withdraw empty stake", func(t *testing.T) { - trx := tx.NewWithdrawTx(lockTime, val.Address(), addr, - 1, fee, "can't withdraw empty stake") - - err := exe.Execute(trx, td.sandbox) - assert.ErrorIs(t, err, ErrInsufficientFunds) - }) + updatedSenderVal := td.sandbox.Validator(senderAddr) + updatedReceiverAcc := td.sandbox.Account(receiverAddr) - assert.Zero(t, td.sandbox.Validator(val.Address()).Stake()) - assert.Equal(t, td.sandbox.Account(addr).Balance(), amt) - assert.Zero(t, td.sandbox.Validator(val.Address()).Stake()) - assert.Zero(t, td.sandbox.Validator(val.Address()).Power()) - assert.Equal(t, td.sandbox.Account(addr).Balance(), amt) + assert.Equal(t, totalStake-amt-fee, updatedSenderVal.Stake()) + assert.Equal(t, amt, updatedReceiverAcc.Balance()) td.checkTotalCoin(t, fee) } diff --git a/sandbox/sandbox_test.go b/sandbox/sandbox_test.go index b388f3333..17bf23b16 100644 --- a/sandbox/sandbox_test.go +++ b/sandbox/sandbox_test.go @@ -225,15 +225,13 @@ func TestTotalAccountCounter(t *testing.T) { td := setup(t) t.Run("Should update total account counter", func(t *testing.T) { - assert.Equal(t, td.store.TotalAccounts(), int32(len(td.valKeys)+1)) - - addr1 := td.RandAccAddress() - addr2 := td.RandAccAddress() - acc := td.sandbox.MakeNewAccount(addr1) - assert.Equal(t, acc.Number(), int32(td.sandbox.Committee().Size()+1)) - acc2 := td.sandbox.MakeNewAccount(addr2) - assert.Equal(t, acc2.Number(), int32(td.sandbox.Committee().Size()+2)) - assert.Zero(t, acc2.Balance()) + totalAccs := td.store.TotalAccounts() + + acc1 := td.sandbox.MakeNewAccount(td.RandAccAddress()) + assert.Equal(t, totalAccs, acc1.Number()) + + acc2 := td.sandbox.MakeNewAccount(td.RandAccAddress()) + assert.Equal(t, totalAccs+1, acc2.Number()) }) } @@ -241,20 +239,15 @@ func TestTotalValidatorCounter(t *testing.T) { td := setup(t) t.Run("Should update total validator counter", func(t *testing.T) { - assert.Equal(t, td.store.TotalValidators(), int32(td.sandbox.Committee().Size())) + totalVals := td.store.TotalValidators() pub, _ := td.RandBLSKeyPair() pub2, _ := td.RandBLSKeyPair() val1 := td.sandbox.MakeNewValidator(pub) - val1.UpdateLastBondingHeight(td.sandbox.CurrentHeight()) - assert.Equal(t, val1.Number(), int32(td.sandbox.Committee().Size())) - assert.Equal(t, val1.LastBondingHeight(), td.sandbox.CurrentHeight()) + assert.Equal(t, totalVals, val1.Number()) val2 := td.sandbox.MakeNewValidator(pub2) - val2.UpdateLastBondingHeight(td.sandbox.CurrentHeight() + 1) - assert.Equal(t, val2.Number(), int32(td.sandbox.Committee().Size()+1)) - assert.Equal(t, val2.LastBondingHeight(), td.sandbox.CurrentHeight()+1) - assert.Zero(t, val2.Stake()) + assert.Equal(t, totalVals+1, val2.Number()) }) } diff --git a/state/execution.go b/state/execution.go index 3e6312777..e97942e69 100644 --- a/state/execution.go +++ b/state/execution.go @@ -9,9 +9,7 @@ import ( "github.com/pactus-project/pactus/util/errors" ) -func (st *state) executeBlock(b *block.Block, sb sandbox.Sandbox) error { - exe := execution.NewExecutor() - +func (st *state) executeBlock(b *block.Block, sb sandbox.Sandbox, check bool) error { var subsidyTrx *tx.Tx for i, trx := range b.Transactions() { // The first transaction should be subsidy transaction @@ -27,9 +25,16 @@ func (st *state) executeBlock(b *block.Block, sb sandbox.Sandbox) error { "duplicated subsidy transaction") } - err := exe.Execute(trx, sb) - if err != nil { - return err + if check { + err := execution.CheckAndExecute(trx, sb, true) + if err != nil { + return err + } + } else { + err := execution.Execute(trx, sb) + if err != nil { + return err + } } } diff --git a/state/execution_test.go b/state/execution_test.go index 1613270e9..a45ef36bf 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -76,7 +76,7 @@ func TestExecuteBlock(t *testing.T) { td.state.lastInfo.SortitionSeed(), proposerAddr) sb := td.state.concreteSandbox() - assert.Error(t, td.state.executeBlock(invBlock, sb)) + assert.Error(t, td.state.executeBlock(invBlock, sb, true)) }) t.Run("Has invalid tx", func(t *testing.T) { @@ -88,7 +88,7 @@ func TestExecuteBlock(t *testing.T) { td.state.lastInfo.SortitionSeed(), proposerAddr) sb := td.state.concreteSandbox() - assert.Error(t, td.state.executeBlock(invBlock, sb)) + assert.Error(t, td.state.executeBlock(invBlock, sb, true)) }) t.Run("Subsidy is not first tx", func(t *testing.T) { @@ -100,7 +100,7 @@ func TestExecuteBlock(t *testing.T) { td.state.lastInfo.SortitionSeed(), proposerAddr) sb := td.state.concreteSandbox() - assert.Error(t, td.state.executeBlock(invBlock, sb)) + assert.Error(t, td.state.executeBlock(invBlock, sb, true)) }) t.Run("Has no subsidy", func(t *testing.T) { @@ -111,7 +111,7 @@ func TestExecuteBlock(t *testing.T) { td.state.lastInfo.SortitionSeed(), proposerAddr) sb := td.state.concreteSandbox() - assert.Error(t, td.state.executeBlock(invBlock, sb)) + assert.Error(t, td.state.executeBlock(invBlock, sb, true)) }) t.Run("Two subsidy transactions", func(t *testing.T) { @@ -123,7 +123,7 @@ func TestExecuteBlock(t *testing.T) { td.state.lastInfo.SortitionSeed(), proposerAddr) sb := td.state.concreteSandbox() - assert.Error(t, td.state.executeBlock(invBlock, sb)) + assert.Error(t, td.state.executeBlock(invBlock, sb, true)) }) t.Run("OK", func(t *testing.T) { @@ -134,7 +134,7 @@ func TestExecuteBlock(t *testing.T) { td.state.stateRoot(), td.state.lastInfo.Certificate(), td.state.lastInfo.SortitionSeed(), proposerAddr) sb := td.state.concreteSandbox() - assert.NoError(t, td.state.executeBlock(invBlock, sb)) + assert.NoError(t, td.state.executeBlock(invBlock, sb, true)) // Check if fee is claimed treasury := sb.Account(crypto.TreasuryAddress) diff --git a/state/state.go b/state/state.go index 21d1aa44d..22c2d3f67 100644 --- a/state/state.go +++ b/state/state.go @@ -320,7 +320,6 @@ func (st *state) ProposeBlock(valKey *bls.ValidatorKey, rewardAddr crypto.Addres // Create new sandbox and execute transactions sb := st.concreteSandbox() - exe := execution.NewExecutor() // Re-check all transactions strictly and remove invalid ones txs := st.txPool.PrepareBlockTransactions() @@ -336,7 +335,7 @@ func (st *state) ProposeBlock(valKey *bls.ValidatorKey, rewardAddr crypto.Addres continue } - if err := exe.Execute(txs[i], sb); err != nil { + if err := execution.CheckAndExecute(txs[i], sb, true); err != nil { st.logger.Debug("found invalid transaction", "tx", txs[i], "error", err) txs.Remove(i) i-- @@ -381,7 +380,7 @@ func (st *state) ValidateBlock(blk *block.Block, round int16) error { sb := st.concreteSandbox() - return st.executeBlock(blk, sb) + return st.executeBlock(blk, sb, true) } func (st *state) CommitBlock(blk *block.Block, cert *certificate.BlockCertificate) error { @@ -423,7 +422,7 @@ func (st *state) CommitBlock(blk *block.Block, cert *certificate.BlockCertificat // ----------------------------------- // Execute block sb := st.concreteSandbox() - if err := st.executeBlock(blk, sb); err != nil { + if err := st.executeBlock(blk, sb, false); err != nil { return err } diff --git a/txpool/txpool.go b/txpool/txpool.go index 5ae70b178..e0b725b7c 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -20,7 +20,6 @@ type txPool struct { lk sync.RWMutex config *Config - checker *execution.Execution sandbox sandbox.Sandbox pools map[payload.Type]pool broadcastCh chan message.Message @@ -37,7 +36,6 @@ func NewTxPool(conf *Config, broadcastCh chan message.Message) TxPool { pool := &txPool{ config: conf, - checker: execution.NewChecker(), pools: pools, broadcastCh: broadcastCh, } @@ -126,7 +124,7 @@ func (p *txPool) appendTx(trx *tx.Tx) error { } func (p *txPool) checkTx(trx *tx.Tx) error { - if err := p.checker.Execute(trx, p.sandbox); err != nil { + if err := execution.CheckAndExecute(trx, p.sandbox, false); err != nil { p.logger.Debug("invalid transaction", "tx", trx, "error", err) return err diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index f057145d2..f430e48f5 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -224,7 +224,7 @@ func TestAddSubsidyTransactions(t *testing.T) { err := td.pool.AppendTx(trx1) assert.ErrorIs(t, err, AppendError{ - Err: execution.PastLockTimeError{ + Err: execution.LockTimeExpiredError{ LockTime: randHeight, }, }) diff --git a/util/errors/errors.go b/util/errors/errors.go index f5d973d10..8efa069db 100644 --- a/util/errors/errors.go +++ b/util/errors/errors.go @@ -14,7 +14,6 @@ const ( ErrInvalidPrivateKey ErrInvalidSignature ErrInvalidTx - ErrInvalidProof ErrInvalidHeight ErrInvalidRound ErrInvalidProposal @@ -35,7 +34,6 @@ var messages = map[int]string{ ErrInvalidPrivateKey: "invalid private key", ErrInvalidSignature: "invalid signature", ErrInvalidTx: "invalid transaction", - ErrInvalidProof: "invalid proof", ErrInvalidHeight: "invalid height", ErrInvalidRound: "invalid round", ErrInvalidProposal: "invalid proposal", diff --git a/util/testsuite/testsuite.go b/util/testsuite/testsuite.go index 9403ad12c..b6a599014 100644 --- a/util/testsuite/testsuite.go +++ b/util/testsuite/testsuite.go @@ -156,7 +156,7 @@ func (ts *TestSuite) RandRound() int16 { return ts.RandInt16(10) } -// RandAmount returns a random amount between [0, 100^e9). +// RandAmount returns a random amount between [0, 1000). func (ts *TestSuite) RandAmount() amount.Amount { return ts.RandAmountRange(0, 1000e9) } @@ -168,9 +168,9 @@ func (ts *TestSuite) RandAmountRange(min, max amount.Amount) amount.Amount { return amt + min } -// RandFee returns a random fee between [0.1, 1). +// RandFee returns a random fee between [0, 1). func (ts *TestSuite) RandFee() amount.Amount { - fee := amount.Amount(ts.RandInt64(0.9e9) + 0.1e9) + fee := amount.Amount(ts.RandInt64(1e9)) return fee } @@ -473,10 +473,11 @@ func (ts *TestSuite) GenerateTestProposal(height uint32, round int16) (*proposal } type TransactionMaker struct { - Amount amount.Amount - Fee amount.Amount - PrvKey *bls.PrivateKey - PubKey *bls.PublicKey + LockTime uint32 + Amount amount.Amount + Fee amount.Amount + PrvKey *bls.PrivateKey + PubKey *bls.PublicKey } // NewTransactionMaker creates a new TransactionMaker instance with default values. @@ -484,10 +485,18 @@ func (ts *TestSuite) NewTransactionMaker() *TransactionMaker { pub, prv := ts.RandBLSKeyPair() return &TransactionMaker{ - Amount: ts.RandAmount(), - Fee: ts.RandFee(), - PrvKey: prv, - PubKey: pub, + LockTime: ts.RandHeight(), + Amount: ts.RandAmount(), + Fee: ts.RandFee(), + PrvKey: prv, + PubKey: pub, + } +} + +// TransactionWithLockTime sets lock-time to the transaction. +func TransactionWithLockTime(lockTime uint32) func(tm *TransactionMaker) { + return func(tm *TransactionMaker) { + tm.LockTime = lockTime } } @@ -520,7 +529,7 @@ func (ts *TestSuite) GenerateTestTransferTx(options ...func(tm *TransactionMaker for _, opt := range options { opt(tm) } - trx := tx.NewTransferTx(ts.RandHeight(), tm.PubKey.AccountAddress(), ts.RandAccAddress(), + trx := tx.NewTransferTx(tm.LockTime, tm.PubKey.AccountAddress(), ts.RandAccAddress(), tm.Amount, tm.Fee, "test send-tx") ts.HelperSignTransaction(tm.PrvKey, trx) @@ -534,7 +543,7 @@ func (ts *TestSuite) GenerateTestBondTx(options ...func(tm *TransactionMaker)) * for _, opt := range options { opt(tm) } - trx := tx.NewBondTx(ts.RandHeight(), tm.PubKey.AccountAddress(), ts.RandValAddress(), + trx := tx.NewBondTx(tm.LockTime, tm.PubKey.AccountAddress(), ts.RandValAddress(), nil, tm.Amount, tm.Fee, "test bond-tx") ts.HelperSignTransaction(tm.PrvKey, trx) @@ -549,7 +558,7 @@ func (ts *TestSuite) GenerateTestSortitionTx(options ...func(tm *TransactionMake opt(tm) } proof := ts.RandProof() - trx := tx.NewSortitionTx(ts.RandHeight(), tm.PubKey.ValidatorAddress(), proof) + trx := tx.NewSortitionTx(tm.LockTime, tm.PubKey.ValidatorAddress(), proof) ts.HelperSignTransaction(tm.PrvKey, trx) return trx @@ -562,7 +571,7 @@ func (ts *TestSuite) GenerateTestUnbondTx(options ...func(tm *TransactionMaker)) for _, opt := range options { opt(tm) } - trx := tx.NewUnbondTx(ts.RandHeight(), tm.PubKey.ValidatorAddress(), "test unbond-tx") + trx := tx.NewUnbondTx(tm.LockTime, tm.PubKey.ValidatorAddress(), "test unbond-tx") ts.HelperSignTransaction(tm.PrvKey, trx) return trx @@ -575,7 +584,7 @@ func (ts *TestSuite) GenerateTestWithdrawTx(options ...func(tm *TransactionMaker for _, opt := range options { opt(tm) } - trx := tx.NewWithdrawTx(ts.RandHeight(), tm.PubKey.ValidatorAddress(), ts.RandAccAddress(), + trx := tx.NewWithdrawTx(tm.LockTime, tm.PubKey.ValidatorAddress(), ts.RandAccAddress(), tm.Amount, tm.Fee, "test withdraw-tx") ts.HelperSignTransaction(tm.PrvKey, trx) From c54b926e4e5d209fb34539f1aa95b99d7b25c815 Mon Sep 17 00:00:00 2001 From: b00f Date: Mon, 22 Jul 2024 13:26:56 +0800 Subject: [PATCH 5/6] fix(store): pruning height returns zero when store is not in prune mode (#1430) --- store/mock.go | 2 +- store/store.go | 30 +++++++++++++++++------------- store/store_test.go | 35 ++++++++++++++++++++++++++++++++--- www/grpc/blockchain.go | 7 +------ www/grpc/blockchain_test.go | 2 ++ 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/store/mock.go b/store/mock.go index eefa4834a..f5e773cf6 100644 --- a/store/mock.go +++ b/store/mock.go @@ -299,5 +299,5 @@ func (*MockStore) IsPruned() bool { } func (*MockStore) PruningHeight() uint32 { - return 1 + return 0 } diff --git a/store/store.go b/store/store.go index d9f9f181b..c5e35b91c 100644 --- a/store/store.go +++ b/store/store.go @@ -415,11 +415,27 @@ func (s *store) IsBanned(addr crypto.Address) bool { return s.config.BannedAddrs[addr] } -// IsPruned indicates that store is in prune mode. +// IsPruned returns true if the store is in prune mode, otherwise false. func (s *store) IsPruned() bool { return s.isPruned } +// PruningHeight returns the height at which blocks will be pruned if the store is in prune mode. +// If the store is not in prune mode, it returns 0. +func (s *store) PruningHeight() uint32 { + s.lk.RLock() + defer s.lk.RUnlock() + + if !s.isPruned { + return 0 + } + + // TODO: it can be optimized (and safer?) by keeping the last block height in memory. + cert := s.lastCertificate() + + return cert.Height() - s.config.RetentionBlocks() +} + // Prune iterates over all blocks from the pruning height to the genesis block and prunes them. // The pruning height is `LastBlockHeight - RetentionBlocks`. // The callback function is called after each block is pruned and can cancel the process. @@ -487,15 +503,3 @@ func (s *store) pruneBlock(blockHeight uint32) (bool, error) { return true, nil } - -func (s *store) PruningHeight() uint32 { - s.lk.RLock() - defer s.lk.RUnlock() - - cert := s.lastCertificate() - if cert == nil { - return 0 - } - - return cert.Height() - s.config.RetentionBlocks() -} diff --git a/store/store_test.go b/store/store_test.go index 7483536e1..fef48e803 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -43,6 +43,7 @@ func setup(t *testing.T, config *Config) *testData { s, err := NewStore(config) require.NoError(t, err) assert.False(t, s.IsPruned(), "empty store should not be in prune mode") + assert.Zero(t, s.PruningHeight(), "pruning height should be zero for an empty store") td := &testData{ TestSuite: ts, @@ -65,6 +66,7 @@ func TestReopenStore(t *testing.T) { store, _ := NewStore(td.store.config) assert.False(t, store.IsPruned()) + assert.Zero(t, store.PruningHeight()) assert.Equal(t, uint32(10), store.LastCertificate().Height()) } @@ -297,7 +299,6 @@ func TestPrune(t *testing.T) { // Store doesn't have blocks for one day err := td.store.Prune(cb) assert.NoError(t, err) - assert.False(t, td.store.isPruned) assert.Zero(t, totalPruned) assert.Zero(t, lastPruningHeight) @@ -330,9 +331,10 @@ func TestPrune(t *testing.T) { td.store.config.TxCacheWindow = 1 s, err := NewStore(td.store.config) require.NoError(t, err) - assert.True(t, s.IsPruned(), "store should be in prune mode") - td.store = s.(*store) + + assert.True(t, td.store.IsPruned(), "store should be in prune mode") + assert.Equal(t, uint32(8), td.store.PruningHeight()) }) t.Run("Commit new block", func(t *testing.T) { @@ -344,5 +346,32 @@ func TestPrune(t *testing.T) { cBlk, err := td.store.Block(9) assert.Error(t, err) assert.Nil(t, cBlk) + + assert.Equal(t, uint32(9), td.store.PruningHeight()) + }) +} + +func TestCancelPrune(t *testing.T) { + conf := testConfig() + conf.RetentionDays = 1 + td := setup(t, conf) + + hits := uint32(0) + cb := func(_ bool, _ uint32) bool { + hits++ + + return true // Cancel pruning + } + + t.Run("Cancel Pruning database", func(t *testing.T) { + blk, cert := td.GenerateTestBlock(blockPerDay + 7) + td.store.SaveBlock(blk, cert) + err := td.store.WriteBatch() + require.NoError(t, err) + + err = td.store.Prune(cb) + assert.NoError(t, err) + + assert.Equal(t, uint32(1), hits) }) } diff --git a/www/grpc/blockchain.go b/www/grpc/blockchain.go index de1b62ddb..c01736c73 100644 --- a/www/grpc/blockchain.go +++ b/www/grpc/blockchain.go @@ -33,11 +33,6 @@ func (s *blockchainServer) GetBlockchainInfo(_ context.Context, cv = append(cv, s.validatorToProto(v)) } - pruningHeight := uint32(0) - if s.state.IsPruned() { - pruningHeight = s.state.PruningHeight() - } - return &pactus.GetBlockchainInfoResponse{ LastBlockHeight: s.state.LastBlockHeight(), LastBlockHash: s.state.LastBlockHash().String(), @@ -46,7 +41,7 @@ func (s *blockchainServer) GetBlockchainInfo(_ context.Context, TotalPower: s.state.TotalPower(), CommitteePower: s.state.CommitteePower(), IsPruned: s.state.IsPruned(), - PruningHeight: pruningHeight, + PruningHeight: s.state.PruningHeight(), LastBlockTime: s.state.LastBlockTime().Unix(), CommitteeValidators: cv, }, nil diff --git a/www/grpc/blockchain_test.go b/www/grpc/blockchain_test.go index df9c5da84..1e8a16850 100644 --- a/www/grpc/blockchain_test.go +++ b/www/grpc/blockchain_test.go @@ -161,6 +161,8 @@ func TestGetBlockchainInfo(t *testing.T) { assert.NoError(t, err) assert.Equal(t, td.mockState.TestStore.LastHeight, res.LastBlockHeight) assert.NotEmpty(t, res.LastBlockHash) + assert.Zero(t, res.PruningHeight) + assert.False(t, res.IsPruned) }) assert.Nil(t, conn.Close(), "Error closing connection") From 43dcd1b4eab042d0dbdb1441152a35a446a4297c Mon Sep 17 00:00:00 2001 From: Javad Rajabzadeh Date: Wed, 24 Jul 2024 18:47:47 +0330 Subject: [PATCH 6/6] feat(cmd): add node type page to the startup assistant (#1431) --- cmd/cmd.go | 6 +- cmd/daemon/import.go | 87 ++++----- cmd/daemon/prune.go | 7 +- cmd/daemon/start.go | 5 +- cmd/downlaod_mgr.go | 225 ------------------------ cmd/gtk/main.go | 7 +- cmd/gtk/startup_assistant.go | 331 ++++++++++++++++++++++++++++++----- cmd/gtk/widget_node.go | 2 +- cmd/importer.go | 239 +++++++++++++++++++++++++ scripts/snapshot.py | 106 +++++++---- util/io.go | 30 ++++ util/io_test.go | 82 +++++++++ util/utils.go | 4 +- 13 files changed, 749 insertions(+), 382 deletions(-) delete mode 100644 cmd/downlaod_mgr.go create mode 100644 cmd/importer.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 9e873a1c1..e1cf0fbf9 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -634,7 +634,7 @@ func MakeValidatorKey(walletInstance *wallet.Wallet, valAddrsInfo []vault.Addres return valKeys, nil } -func TerminalProgressBar(totalSize, barWidth int, showBytes bool) *progressbar.ProgressBar { +func TerminalProgressBar(totalSize int64, barWidth int) *progressbar.ProgressBar { if barWidth < 15 { barWidth = 15 } @@ -642,10 +642,10 @@ func TerminalProgressBar(totalSize, barWidth int, showBytes bool) *progressbar.P opts := []progressbar.Option{ progressbar.OptionSetWriter(ansi.NewAnsiStdout()), progressbar.OptionEnableColorCodes(true), - progressbar.OptionShowBytes(showBytes), progressbar.OptionSetWidth(barWidth), progressbar.OptionSetElapsedTime(false), progressbar.OptionSetPredictTime(false), + progressbar.OptionShowDescriptionAtLineEnd(), progressbar.OptionSetTheme(progressbar.Theme{ Saucer: "[green]=[reset]", SaucerHead: "[green]>[reset]", @@ -655,5 +655,5 @@ func TerminalProgressBar(totalSize, barWidth int, showBytes bool) *progressbar.P }), } - return progressbar.NewOptions(totalSize, opts...) + return progressbar.NewOptions64(totalSize, opts...) } diff --git a/cmd/daemon/import.go b/cmd/daemon/import.go index b9eebe7f1..c52a6cc7b 100644 --- a/cmd/daemon/import.go +++ b/cmd/daemon/import.go @@ -4,11 +4,9 @@ import ( "fmt" "os" "path/filepath" - "time" "github.com/gofrs/flock" "github.com/pactus-project/pactus/cmd" - "github.com/pactus-project/pactus/genesis" "github.com/pactus-project/pactus/util" "github.com/spf13/cobra" ) @@ -21,7 +19,7 @@ func buildImportCmd(parentCmd *cobra.Command) { parentCmd.AddCommand(importCmd) workingDirOpt := addWorkingDirOption(importCmd) - serverAddrOpt := importCmd.Flags().String("server-addr", "https://download.pactus.org", + serverAddrOpt := importCmd.Flags().String("server-addr", cmd.DefaultSnapshotURL, "import server address") importCmd.Run = func(c *cobra.Command, _ []string) { @@ -46,39 +44,25 @@ func buildImportCmd(parentCmd *cobra.Command) { return } - storeDir, _ := filepath.Abs(conf.Store.StorePath()) - if !util.IsDirNotExistsOrEmpty(storeDir) { - cmd.PrintErrorMsgf("The data directory is not empty: %s", conf.Store.StorePath()) - - return - } + cmd.PrintLine() snapshotURL := *serverAddrOpt + importer, err := cmd.NewImporter( + gen.ChainType(), + snapshotURL, + conf.Store.DataPath(), + ) + cmd.FatalErrorCheck(err) - switch gen.ChainType() { - case genesis.Mainnet: - snapshotURL += "/mainnet/" - case genesis.Testnet: - snapshotURL += "/testnet/" - case genesis.Localnet: - cmd.PrintErrorMsgf("Unsupported chain type: %s", gen.ChainType()) - - return - } - - metadata, err := cmd.GetSnapshotMetadata(c.Context(), snapshotURL) - if err != nil { - cmd.PrintErrorMsgf("Failed to get snapshot metadata: %s", err) - - return - } + metadata, err := importer.GetMetadata(c.Context()) + cmd.FatalErrorCheck(err) snapshots := make([]string, 0, len(metadata)) for _, m := range metadata { item := fmt.Sprintf("snapshot %s (%s)", - parseTime(m.CreatedAt).Format("2006-01-02"), - util.FormatBytesToHumanReadable(m.TotalSize), + m.CreatedAtTime().Format("2006-01-02"), + util.FormatBytesToHumanReadable(m.Data.Size), ) snapshots = append(snapshots, item) @@ -89,34 +73,32 @@ func buildImportCmd(parentCmd *cobra.Command) { choice := cmd.PromptSelect("Please select a snapshot", snapshots) selected := metadata[choice] - tmpDir := util.TempDirPath() - extractPath := fmt.Sprintf("%s/data", tmpDir) - err = os.MkdirAll(extractPath, 0o750) - cmd.FatalErrorCheck(err) + cmd.TrapSignal(func() { + _ = fileLock.Unlock() + _ = importer.Cleanup() + }) cmd.PrintLine() - zipFileList := cmd.DownloadManager( + importer.Download( c.Context(), &selected, - snapshotURL, - tmpDir, downloadProgressBar, ) - for _, zFile := range zipFileList { - err := cmd.ExtractAndStoreFile(zFile, extractPath) - cmd.FatalErrorCheck(err) - } + cmd.PrintLine() + cmd.PrintLine() + cmd.PrintInfoMsgf("Extracting files...") - err = os.MkdirAll(filepath.Dir(conf.Store.StorePath()), 0o750) + err = importer.ExtractAndStoreFiles() cmd.FatalErrorCheck(err) - err = cmd.CopyAllFiles(extractPath, conf.Store.StorePath()) + cmd.PrintInfoMsgf("Moving data...") + err = importer.MoveStore() cmd.FatalErrorCheck(err) - err = os.RemoveAll(tmpDir) + err = importer.Cleanup() cmd.FatalErrorCheck(err) _ = fileLock.Unlock() @@ -131,19 +113,12 @@ func buildImportCmd(parentCmd *cobra.Command) { } func downloadProgressBar(fileName string, totalSize, downloaded int64, _ float64) { - bar := cmd.TerminalProgressBar(int(totalSize), 30, true) - bar.Describe(fileName) - err := bar.Add(int(downloaded)) + bar := cmd.TerminalProgressBar(totalSize, 30) + bar.Describe(fmt.Sprintf("%s (%s/%s)", + fileName, + util.FormatBytesToHumanReadable(uint64(downloaded)), + util.FormatBytesToHumanReadable(uint64(totalSize)), + )) + err := bar.Add64(downloaded) cmd.FatalErrorCheck(err) } - -func parseTime(dateString string) time.Time { - const layout = "2006-01-02T15:04:05.000000" - - parsedTime, err := time.Parse(layout, dateString) - if err != nil { - return time.Time{} - } - - return parsedTime -} diff --git a/cmd/daemon/prune.go b/cmd/daemon/prune.go index 3fd49ade9..a58c2ab89 100644 --- a/cmd/daemon/prune.go +++ b/cmd/daemon/prune.go @@ -34,10 +34,7 @@ func buildPruneCmd(parentCmd *cobra.Command) { fileLock := flock.New(lockFilePath) locked, err := fileLock.TryLock() - if err != nil { - // handle unable to attempt to acquire lock - cmd.FatalErrorCheck(err) - } + cmd.FatalErrorCheck(err) if !locked { cmd.PrintWarnMsgf("Could not lock '%s', another instance is running?", lockFilePath) @@ -114,7 +111,7 @@ func buildPruneCmd(parentCmd *cobra.Command) { } func pruningProgressBar(prunedCount, skippedCount, totalCount uint32) { - bar := cmd.TerminalProgressBar(int(totalCount), 30, false) + bar := cmd.TerminalProgressBar(int64(totalCount), 30) bar.Describe(fmt.Sprintf("Pruned: %d | Skipped: %d", prunedCount, skippedCount)) err := bar.Add(int(prunedCount + skippedCount)) cmd.FatalErrorCheck(err) diff --git a/cmd/daemon/start.go b/cmd/daemon/start.go index b707cb57d..3df4b2f32 100644 --- a/cmd/daemon/start.go +++ b/cmd/daemon/start.go @@ -41,10 +41,7 @@ func buildStartCmd(parentCmd *cobra.Command) { fileLock := flock.New(lockFilePath) locked, err := fileLock.TryLock() - if err != nil { - // handle unable to attempt to acquire lock - cmd.FatalErrorCheck(err) - } + cmd.FatalErrorCheck(err) if !locked { cmd.PrintWarnMsgf("Could not lock '%s', another instance is running?", lockFilePath) diff --git a/cmd/downlaod_mgr.go b/cmd/downlaod_mgr.go deleted file mode 100644 index ed00b6bba..000000000 --- a/cmd/downlaod_mgr.go +++ /dev/null @@ -1,225 +0,0 @@ -package cmd - -import ( - "archive/zip" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" - - "github.com/pactus-project/pactus/util/downloader" -) - -const maxDecompressedSize = 10 << 20 // 10 MB - -type Metadata struct { - Name string `json:"name"` - CreatedAt string `json:"created_at"` - Compress string `json:"compress"` - TotalSize uint64 `json:"total_size"` - Data []*SnapshotData `json:"data"` -} - -type SnapshotData struct { - Name string `json:"name"` - Path string `json:"path"` - Sha string `json:"sha"` - Size uint64 `json:"size"` -} - -func GetSnapshotMetadata(ctx context.Context, snapshotURL string) ([]Metadata, error) { - cli := http.DefaultClient - metaURL, err := url.JoinPath(snapshotURL, "metadata.json") - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, metaURL, http.NoBody) - if err != nil { - return nil, err - } - - resp, err := cli.Do(req) - if err != nil { - return nil, err - } - defer func() { - _ = resp.Body.Close() - }() - - if resp.StatusCode != http.StatusOK { - return nil, errors.New(resp.Status) - } - - metadata := make([]Metadata, 0) - - dec := json.NewDecoder(resp.Body) - - if err := dec.Decode(&metadata); err != nil { - return nil, err - } - - return metadata, nil -} - -func DownloadManager( - ctx context.Context, - metadata *Metadata, - baseURL, tempPath string, - stateFunc func(fileName string, totalSize, downloaded int64, percentage float64), -) []string { - zipFileListPath := make([]string, 0) - - for _, data := range metadata.Data { - done := make(chan struct{}) - dlLink, err := url.JoinPath(baseURL, data.Path) - FatalErrorCheck(err) - - fileName := filepath.Base(dlLink) - - filePath := fmt.Sprintf("%s/%s", tempPath, fileName) - - dl := downloader.New( - dlLink, - filePath, - data.Sha, - ) - - dl.Start(ctx) - - go func() { - err := <-dl.Errors() - FatalErrorCheck(err) - }() - - go func() { - for state := range dl.Stats() { - stateFunc(fileName, state.TotalSize, state.Downloaded, state.Percent) - if state.Completed { - done <- struct{}{} - close(done) - - return - } - } - }() - - <-done - zipFileListPath = append(zipFileListPath, filePath) - } - - return zipFileListPath -} - -func ExtractAndStoreFile(zipFilePath, extractPath string) error { - r, err := zip.OpenReader(zipFilePath) - if err != nil { - return fmt.Errorf("failed to open zip file: %w", err) - } - defer func() { - _ = r.Close() - }() - - for _, f := range r.File { - rc, err := f.Open() - if err != nil { - return fmt.Errorf("failed to open file in zip archive: %w", err) - } - - fpath := fmt.Sprintf("%s/%s", extractPath, f.Name) - - outFile, err := os.Create(fpath) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - - // fixed potential DoS vulnerability via decompression bomb - lr := io.LimitedReader{R: rc, N: maxDecompressedSize} - _, err = io.Copy(outFile, &lr) - if err != nil { - return fmt.Errorf("failed to copy file contents: %w", err) - } - - // check if the file size exceeds the limit - if lr.N <= 0 { - return fmt.Errorf("file exceeds maximum decompressed size limit: %s", fpath) - } - - _ = rc.Close() - _ = outFile.Close() - } - - return nil -} - -// CopyAllFiles copies all files from srcDir to dstDir. -func CopyAllFiles(srcDir, dstDir string) error { - err := os.MkdirAll(dstDir, 0o750) - if err != nil { - return fmt.Errorf("failed to create destination directory: %w", err) - } - - return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil // Skip directories - } - - relativePath, err := filepath.Rel(srcDir, path) - if err != nil { - return err - } - - dstPath := filepath.Join(dstDir, relativePath) - - err = os.MkdirAll(filepath.Dir(dstPath), 0o750) - if err != nil { - return fmt.Errorf("failed to create directory: %w", err) - } - - err = copyFile(path, dstPath) - if err != nil { - return fmt.Errorf("failed to copy file from %s to %s: %w", path, dstPath, err) - } - - return nil - }) -} - -func copyFile(src, dst string) error { - sourceFile, err := os.Open(src) - if err != nil { - return fmt.Errorf("failed to open source file: %w", err) - } - defer func() { - _ = sourceFile.Close() - }() - - destinationFile, err := os.Create(dst) - if err != nil { - return fmt.Errorf("failed to create destination file: %w", err) - } - defer func() { - _ = destinationFile.Close() - }() - - _, err = io.Copy(destinationFile, sourceFile) - if err != nil { - return fmt.Errorf("failed to copy file contents: %w", err) - } - - err = destinationFile.Sync() - if err != nil { - return fmt.Errorf("failed to sync destination file: %w", err) - } - - return nil -} diff --git a/cmd/gtk/main.go b/cmd/gtk/main.go index 451466828..c9bf57945 100644 --- a/cmd/gtk/main.go +++ b/cmd/gtk/main.go @@ -48,7 +48,7 @@ func main() { } // If node is not initialized yet - if !util.PathExists(workingDir) { + if util.IsDirNotExistsOrEmpty(workingDir) { network := genesis.Mainnet if *testnetOpt { network = genesis.Testnet @@ -63,10 +63,7 @@ func main() { fileLock := flock.New(lockFilePath) locked, err := fileLock.TryLock() - if err != nil { - // handle unable to attempt to acquire lock - fatalErrorCheck(err) - } + fatalErrorCheck(err) if !locked { cmd.PrintWarnMsgf("Could not lock '%s', another instance is running?", lockFilePath) diff --git a/cmd/gtk/startup_assistant.go b/cmd/gtk/startup_assistant.go index cfa910b51..a0d0112cc 100644 --- a/cmd/gtk/startup_assistant.go +++ b/cmd/gtk/startup_assistant.go @@ -3,15 +3,19 @@ package main import ( + "context" "fmt" "log" + "path/filepath" "regexp" "strings" + "time" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" "github.com/pactus-project/pactus/cmd" "github.com/pactus-project/pactus/genesis" + "github.com/pactus-project/pactus/util" "github.com/pactus-project/pactus/wallet" ) @@ -25,8 +29,8 @@ func setMargin(widget gtk.IWidget, top, bottom, start, end int) { widget.ToWidget().SetMarginEnd(end) } -//nolint:gocognit // complexity can't be reduced more. -func startupAssistant(workingDir string, chain genesis.ChainType) bool { +//nolint:all // complexity can't be reduced more. It needs to refactor. +func startupAssistant(workingDir string, chainType genesis.ChainType) bool { successful := false assistant, err := gtk.AssistantNew() fatalErrorCheck(err) @@ -37,25 +41,29 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { assistFunc := pageAssistant() // --- page_mode - mode, restoreRadio, pageModeName := pageMode(assistant, assistFunc) + wgtWalletMode, radioRestoreWallet, pageModeName := pageWalletMode(assistant, assistFunc) // --- page_seed_generate - seedGenerate, textViewSeed, pageSeedGenerateName := pageSeedGenerate(assistant, assistFunc) + wgtSeedGenerate, txtSeed, pageSeedGenerateName := pageSeedGenerate(assistant, assistFunc) // --- page_seed_confirm - seedConfirm, pageSeedConfirmName := pageSeedConfirm(assistant, assistFunc, textViewSeed) + wgtSeedConfirm, pageSeedConfirmName := pageSeedConfirm(assistant, assistFunc, txtSeed) // -- page_seed_restore - seedRestore, textViewRestoreSeed, pageSeedRestoreName := pageSeedRestore(assistant, assistFunc) + wgtSeedRestore, textRestoreSeed, pageSeedRestoreName := pageSeedRestore(assistant, assistFunc) // --- page_password - password, entryPassword, pagePasswordName := pagePassword(assistant, assistFunc) + wgtPassword, entryPassword, pagePasswordName := pagePassword(assistant, assistFunc) // --- page_num_validators - numValidators, lsNumValidators, comboNumValidators, pageNumValidatorsName := pageNumValidators(assistant, assistFunc) + wgtNumValidators, lsNumValidators, comboNumValidators, + pageNumValidatorsName := pageNumValidators(assistant, assistFunc) - // --- page_final - final, textViewNodeInfo, pageFinalName := pageFinal(assistant, assistFunc) + // -- page_node_type + wgtNodeType, gridImport, radioImport, pageNodeTypeName := pageNodeType(assistant, assistFunc) + + // --- page_summary + wgtSummary, txtNodeInfo, pageSummaryName := pageSummary(assistant, assistFunc) assistant.Connect("cancel", func() { assistant.Close() @@ -68,19 +76,20 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { gtk.MainQuit() }) - assistant.SetPageType(mode, gtk.ASSISTANT_PAGE_INTRO) // page 0 - assistant.SetPageType(seedGenerate, gtk.ASSISTANT_PAGE_CONTENT) // page 1 - assistant.SetPageType(seedConfirm, gtk.ASSISTANT_PAGE_CONTENT) // page 2 - assistant.SetPageType(seedRestore, gtk.ASSISTANT_PAGE_CONTENT) // page 3 - assistant.SetPageType(password, gtk.ASSISTANT_PAGE_CONTENT) // page 4 - assistant.SetPageType(numValidators, gtk.ASSISTANT_PAGE_CONTENT) // page 5 - assistant.SetPageType(final, gtk.ASSISTANT_PAGE_SUMMARY) // page 6 + assistant.SetPageType(wgtWalletMode, gtk.ASSISTANT_PAGE_INTRO) // page 0 + assistant.SetPageType(wgtSeedGenerate, gtk.ASSISTANT_PAGE_CONTENT) // page 1 + assistant.SetPageType(wgtSeedConfirm, gtk.ASSISTANT_PAGE_CONTENT) // page 2 + assistant.SetPageType(wgtSeedRestore, gtk.ASSISTANT_PAGE_CONTENT) // page 3 + assistant.SetPageType(wgtPassword, gtk.ASSISTANT_PAGE_CONTENT) // page 4 + assistant.SetPageType(wgtNumValidators, gtk.ASSISTANT_PAGE_CONTENT) // page 5 + assistant.SetPageType(wgtNodeType, gtk.ASSISTANT_PAGE_CONTENT) // page 6 + assistant.SetPageType(wgtSummary, gtk.ASSISTANT_PAGE_SUMMARY) // page 7 mnemonic := "" prevPageIndex := -1 prevPageAdjust := 0 assistant.Connect("prepare", func(assistant *gtk.Assistant, page *gtk.Widget) { - isRestoreMode := restoreRadio.GetActive() + isRestoreMode := radioRestoreWallet.GetActive() curPageName, err := page.GetName() curPageIndex := assistant.GetCurrentPage() fatalErrorCheck(err) @@ -94,7 +103,7 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { curPageName, isRestoreMode, prevPageIndex, curPageIndex) switch curPageName { case pageModeName: - assistantPageComplete(assistant, mode, true) + assistantPageComplete(assistant, wgtWalletMode, true) case pageSeedGenerateName: if isRestoreMode { @@ -109,11 +118,11 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { assistant.PreviousPage() prevPageAdjust = -1 } - assistantPageComplete(assistant, seedGenerate, false) + assistantPageComplete(assistant, wgtSeedGenerate, false) } else { mnemonic, _ = wallet.GenerateMnemonic(128) - setTextViewContent(textViewSeed, mnemonic) - assistantPageComplete(assistant, seedGenerate, true) + setTextViewContent(txtSeed, mnemonic) + assistantPageComplete(assistant, wgtSeedGenerate, true) } case pageSeedConfirmName: if isRestoreMode { @@ -128,9 +137,9 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { assistant.PreviousPage() prevPageAdjust = -1 } - assistantPageComplete(assistant, seedConfirm, false) + assistantPageComplete(assistant, wgtSeedConfirm, false) } else { - assistantPageComplete(assistant, seedConfirm, false) + assistantPageComplete(assistant, wgtSeedConfirm, false) } case pageSeedRestoreName: if !isRestoreMode { @@ -145,24 +154,161 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { assistant.PreviousPage() prevPageAdjust = -1 } - assistantPageComplete(assistant, seedConfirm, false) + assistantPageComplete(assistant, wgtSeedConfirm, false) } else { - assistantPageComplete(assistant, seedRestore, true) + assistantPageComplete(assistant, wgtSeedRestore, true) } case pagePasswordName: if isRestoreMode { - mnemonic = getTextViewContent(textViewRestoreSeed) + mnemonic = getTextViewContent(textRestoreSeed) if err := wallet.CheckMnemonic(mnemonic); err != nil { showErrorDialog(assistant, "mnemonic is invalid") assistant.PreviousPage() } } - assistantPageComplete(assistant, password, true) + assistantPageComplete(assistant, wgtPassword, true) case pageNumValidatorsName: - assistantPageComplete(assistant, numValidators, true) + assistantPageComplete(assistant, wgtNumValidators, true) + + case pageNodeTypeName: + assistantPageComplete(assistant, wgtNodeType, true) + ssLabel, err := gtk.LabelNew("") + fatalErrorCheck(err) + setMargin(ssLabel, 5, 5, 1, 1) + ssLabel.SetHAlign(gtk.ALIGN_START) + + listBox, err := gtk.ListBoxNew() + fatalErrorCheck(err) + setMargin(listBox, 5, 5, 1, 1) + listBox.SetHAlign(gtk.ALIGN_CENTER) + listBox.SetSizeRequest(600, -1) + + ssDLBtn, err := gtk.ButtonNewWithLabel("⏬ Download") + fatalErrorCheck(err) + setMargin(ssDLBtn, 10, 5, 1, 1) + ssDLBtn.SetHAlign(gtk.ALIGN_CENTER) + ssDLBtn.SetSizeRequest(600, -1) - case pageFinalName: + ssPBLabel, err := gtk.LabelNew("") + fatalErrorCheck(err) + setMargin(ssPBLabel, 5, 10, 1, 1) + ssPBLabel.SetHAlign(gtk.ALIGN_START) + + gridImport.Attach(ssLabel, 0, 1, 1, 1) + gridImport.Attach(listBox, 0, 2, 1, 1) + gridImport.Attach(ssDLBtn, 0, 3, 1, 1) + gridImport.Attach(ssPBLabel, 0, 5, 1, 1) + ssLabel.SetVisible(false) + listBox.SetVisible(false) + ssDLBtn.SetVisible(false) + ssPBLabel.SetVisible(false) + + snapshotIndex := 0 + + radioImport.Connect("toggled", func() { + if radioImport.GetActive() { + assistantPageComplete(assistant, wgtNodeType, false) + + ssLabel.SetVisible(true) + ssLabel.SetText(" ♻️ Please wait, loading snapshot list...") + + go func() { + time.Sleep(1 * time.Second) + + glib.IdleAdd(func() { + snapshotURL := cmd.DefaultSnapshotURL // TODO: make me optional... + + storeDir := filepath.Join(workingDir, "data") + importer, err := cmd.NewImporter( + chainType, + snapshotURL, + storeDir, + ) + fatalErrorCheck(err) + + ctx := context.Background() + mdCh := getMetadata(ctx, importer, listBox) + + if md := <-mdCh; md == nil { + ssLabel.SetText(" ❌ Failed to get snapshot list, please try again later.") + } else { + ssLabel.SetText(" 🔽 Please select a snapshot to download:") + listBox.SetVisible(true) + + listBox.Connect("row-selected", func(_ *gtk.ListBox, row *gtk.ListBoxRow) { + if row != nil { + snapshotIndex = row.GetIndex() + ssDLBtn.SetVisible(true) + } + }) + + ssDLBtn.Connect("clicked", func() { + radioGroup, _ := radioImport.GetParent() + radioImport.SetSensitive(false) + radioGroup.ToWidget().SetSensitive(false) + ssLabel.SetSensitive(false) + listBox.SetSensitive(false) + ssDLBtn.SetSensitive(false) + + ssDLBtn.SetVisible(false) + ssPBLabel.SetVisible(true) + listBox.SetSelectionMode(gtk.SELECTION_NONE) + + go func() { + log.Printf("start downloading...\n") + + importer.Download( + ctx, + &md[snapshotIndex], + func(fileName string, totalSize, downloaded int64, + percentage float64, + ) { + percent := int(percentage) + glib.IdleAdd(func() { + dlMessage := fmt.Sprintf("🌐 Downloading %s | %d%% (%s / %s)", + fileName, + percent, + util.FormatBytesToHumanReadable(uint64(downloaded)), + util.FormatBytesToHumanReadable(uint64(totalSize)), + ) + ssPBLabel.SetText(" " + dlMessage) + }) + }, + ) + + glib.IdleAdd(func() { + log.Printf("extracting data...\n") + ssPBLabel.SetText(" " + "📂 Extracting downloaded files...") + err := importer.ExtractAndStoreFiles() + fatalErrorCheck(err) + + log.Printf("moving data...\n") + ssPBLabel.SetText(" " + "📑 Moving data...") + err = importer.MoveStore() + fatalErrorCheck(err) + + log.Printf("cleanup...\n") + err = importer.Cleanup() + fatalErrorCheck(err) + + ssPBLabel.SetText(" " + "✅ Import completed.") + assistantPageComplete(assistant, wgtNodeType, true) + }) + }() + }) + } + }) + }() + } else { + assistantPageComplete(assistant, wgtNodeType, true) + ssLabel.SetVisible(false) + listBox.SetVisible(false) + ssDLBtn.SetVisible(false) + ssPBLabel.SetVisible(false) + } + }) + case pageSummaryName: iter, err := comboNumValidators.GetActiveIter() fatalErrorCheck(err) @@ -176,13 +322,13 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { walletPassword, err := entryPassword.GetText() fatalErrorCheck(err) - validatorAddrs, rewardAddrs, err := cmd.CreateNode(numValidators, chain, workingDir, mnemonic, walletPassword) + validatorAddrs, rewardAddrs, err := cmd.CreateNode(numValidators, chainType, workingDir, mnemonic, walletPassword) fatalErrorCheck(err) // Done! showing the node information successful = true nodeInfo := fmt.Sprintf("Working directory: %s\n", workingDir) - nodeInfo += fmt.Sprintf("Network: %s\n", chain.String()) + nodeInfo += fmt.Sprintf("Network: %s\n", chainType.String()) nodeInfo += "\nValidator addresses:\n" for i, addr := range validatorAddrs { nodeInfo += fmt.Sprintf("%v- %s\n", i+1, addr) @@ -193,7 +339,7 @@ func startupAssistant(workingDir string, chain genesis.ChainType) bool { nodeInfo += fmt.Sprintf("%v- %s\n", i+1, addr) } - setTextViewContent(textViewNodeInfo, nodeInfo) + setTextViewContent(txtNodeInfo, nodeInfo) } prevPageIndex = curPageIndex + prevPageAdjust }) @@ -243,7 +389,7 @@ func pageAssistant() assistantFunc { } } -func pageMode(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk.Widget, *gtk.RadioButton, string) { +func pageWalletMode(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk.Widget, *gtk.RadioButton, string) { var mode *gtk.Widget newWalletRadio, err := gtk.RadioButtonNewWithLabel(nil, "Create a new wallet from the scratch") fatalErrorCheck(err) @@ -260,8 +406,8 @@ func pageMode(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk.Widget, radioBox.Add(restoreWalletRadio) setMargin(restoreWalletRadio, 6, 6, 6, 6) - pageModeName := "page_mode" - pageModeTitle := "Initialize mode" + pageModeName := "page_wallet_mode" + pageModeTitle := "Wallet Mode" pageModeSubject := "How to create your wallet?" pageModeDesc := "If you are running the node for the first time, choose the first option." mode = assistFunc( @@ -287,7 +433,7 @@ func pageSeedGenerate(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk. textViewSeed.SetSizeRequest(0, 80) pageSeedName := "page_seed_generate" - pageSeedTitle := "Wallet seed" + pageSeedTitle := "Wallet Seed" pageSeedSubject := "Your wallet generation seed is:" pageSeedDesc := `Please write these 12 words on paper. This seed will allow you to recover your wallet in case of computer failure. @@ -319,7 +465,7 @@ func pageSeedRestore(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk.W textViewRestoreSeed.SetSizeRequest(0, 80) pageSeedName := "page_seed_restore" - pageSeedTitle := "Wallet seed restore" + pageSeedTitle := "Wallet Seed Restore" pageSeedSubject := "Enter your wallet seed:" pageSeedDesc := "Please enter your 12 words mnemonics backup to restore your wallet." @@ -369,7 +515,7 @@ func pageSeedConfirm(assistant *gtk.Assistant, assistFunc assistantFunc, }) pageSeedConfirmName := "page_seed_confirm" - pageSeedConfirmTitle := "Confirm seed" + pageSeedConfirmTitle := "Confirm Seed" pageSeedConfirmSubject := "What was your seed?" pageSeedConfirmDesc := `Your seed is important! To make sure that you have properly saved your seed, please retype it here.` @@ -385,6 +531,59 @@ To make sure that you have properly saved your seed, please retype it here.` return pageWidget, pageSeedConfirmName } +func pageNodeType(assistant *gtk.Assistant, assistFunc assistantFunc) ( + *gtk.Widget, + *gtk.Grid, + *gtk.RadioButton, + string, +) { + var pageWidget *gtk.Widget + + vbox, err := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) + fatalErrorCheck(err) + + grid, err := gtk.GridNew() + fatalErrorCheck(err) + + btnFullNode, err := gtk.RadioButtonNewWithLabel(nil, "Full node") + fatalErrorCheck(err) + btnFullNode.SetActive(true) + + btnPruneNode, err := gtk.RadioButtonNewWithLabelFromWidget(btnFullNode, "Pruned node") + fatalErrorCheck(err) + + radioBox, err := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) + fatalErrorCheck(err) + + radioBox.Add(btnFullNode) + setMargin(btnFullNode, 6, 6, 6, 6) + radioBox.Add(btnPruneNode) + setMargin(btnPruneNode, 6, 10, 6, 6) + + grid.Attach(radioBox, 0, 0, 1, 1) + + vbox.PackStart(grid, true, true, 0) + + pageName := "page_node_type" + pageTitle := "Node Type" + pageSubject := "How do you want to start your node?" + pageDesc := `A pruned node doesn’t keep all the historical data. +Instead, it only retains the most recent part of the blockchain, deleting older data to save disk space. +Offline data is available at: https://snapshot.pactus.org/.` + + // Create and return the page widget using assistFunc + pageWidget = assistFunc( + assistant, + vbox, + pageName, + pageTitle, + pageSubject, + pageDesc, + ) + + return pageWidget, grid, btnPruneNode, pageName +} + func pagePassword(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk.Widget, *gtk.Entry, string) { pageWidget := new(gtk.Widget) entryPassword, err := gtk.EntryNew() @@ -439,7 +638,7 @@ func pagePassword(assistant *gtk.Assistant, assistFunc assistantFunc) (*gtk.Widg }) pagePasswordName := "page_password" - pagePasswordTitle := "Wallet password" + pagePasswordTitle := "Wallet Password" pagePasswordSubject := "Enter password for your wallet:" pagePsswrdDesc := "Please choose a strong password for your wallet." @@ -492,7 +691,7 @@ func pageNumValidators(assistant *gtk.Assistant, grid.Attach(comboNumValidators, 1, 0, 1, 1) pageNumValidatorsName := "page_num_validators" - pageNumValidatorsTitle := "Number of validators" + pageNumValidatorsTitle := "Number of Validators" pageNumValidatorsSubject := "How many validators do you want to create?" pageNumValidatorsDesc := `Each node can run up to 32 validators, and each validator can hold up to 1000 staked coins. You can define validators based on the amount of coins you want to stake. @@ -509,7 +708,7 @@ For more information, look 0 { + child := children.Data().(*gtk.Widget) + listBox.Remove(child) + children = children.Next() + } + + metadata, err := dm.GetMetadata(ctx) + if err != nil { + mdCh <- nil + + return + } + + for _, md := range metadata { + listBoxRow, err := gtk.ListBoxRowNew() + fatalErrorCheck(err) + + label, err := gtk.LabelNew(fmt.Sprintf("snapshot %s (%s)", + md.CreatedAtTime().Format("2006-01-02"), + util.FormatBytesToHumanReadable(md.Data.Size), + )) + fatalErrorCheck(err) + + listBoxRow.Add(label) + listBox.Add(listBoxRow) + } + listBox.ShowAll() + mdCh <- metadata + }() + + return mdCh +} diff --git a/cmd/gtk/widget_node.go b/cmd/gtk/widget_node.go index e340d3cbd..66b1599ea 100644 --- a/cmd/gtk/widget_node.go +++ b/cmd/gtk/widget_node.go @@ -143,7 +143,7 @@ func (wn *widgetNode) timeout10() bool { fatalErrorCheck(err) wn.labelClockOffset.SetTooltipText("Difference between time of your machine and " + - "network time( (NTP) for synchronization.") + "network time (NTP) for synchronization.") if offsetErr != nil { styleContext.AddClass("warning") diff --git a/cmd/importer.go b/cmd/importer.go new file mode 100644 index 000000000..ce8405709 --- /dev/null +++ b/cmd/importer.go @@ -0,0 +1,239 @@ +package cmd + +import ( + "archive/zip" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "sort" + "time" + + "github.com/pactus-project/pactus/genesis" + "github.com/pactus-project/pactus/util" + "github.com/pactus-project/pactus/util/downloader" +) + +const DefaultSnapshotURL = "https://snapshot.pactus.org" + +const maxDecompressedSize = 10 << 20 // 10 MB + +type DMStateFunc func( + fileName string, + totalSize, downloaded int64, + percentage float64, +) + +type Metadata struct { + Name string `json:"name"` + CreatedAt string `json:"created_at"` + Compress string `json:"compress"` + Data SnapshotData `json:"data"` +} + +type SnapshotData struct { + Name string `json:"name"` + Path string `json:"path"` + Sha string `json:"sha"` + Size uint64 `json:"size"` +} + +func (md *Metadata) CreatedAtTime() time.Time { + const layout = "2006-01-02T15:04:05.000000" + + parsedTime, err := time.Parse(layout, md.CreatedAt) + if err != nil { + return time.Time{} + } + + return parsedTime +} + +// Importer downloads and imports the pruned data from a centralized server. +type Importer struct { + snapshotURL string + tempDir string + storeDir string + dataFileName string +} + +func NewImporter(chainType genesis.ChainType, snapshotURL, storeDir string) (*Importer, error) { + if util.PathExists(storeDir) { + return nil, fmt.Errorf("data directory is not empty: %s", storeDir) + } + + switch chainType { + case genesis.Mainnet: + snapshotURL += "/mainnet/" + case genesis.Testnet: + snapshotURL += "/testnet/" + case genesis.Localnet: + return nil, fmt.Errorf("unsupported chain type: %s", chainType) + } + + tempDir := util.TempDirPath() + + return &Importer{ + snapshotURL: snapshotURL, + tempDir: tempDir, + storeDir: storeDir, + }, nil +} + +func (i *Importer) GetMetadata(ctx context.Context) ([]Metadata, error) { + cli := http.DefaultClient + metaURL, err := url.JoinPath(i.snapshotURL, "metadata.json") + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, metaURL, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := cli.Do(req) + if err != nil { + return nil, err + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New(resp.Status) + } + + metadata := make([]Metadata, 0) + + dec := json.NewDecoder(resp.Body) + + if err := dec.Decode(&metadata); err != nil { + return nil, err + } + + sort.SliceStable(metadata, func(i, j int) bool { + return metadata[i].CreatedAtTime().After(metadata[j].CreatedAtTime()) + }) + + return metadata, nil +} + +func (i *Importer) Download( + ctx context.Context, + metadata *Metadata, + stateFunc DMStateFunc, +) { + done := make(chan struct{}) + dlLink, err := url.JoinPath(i.snapshotURL, metadata.Data.Path) + FatalErrorCheck(err) + + fileName := filepath.Base(dlLink) + + i.dataFileName = fileName + + filePath := fmt.Sprintf("%s/%s", i.tempDir, fileName) + + d := downloader.New( + dlLink, + filePath, + metadata.Data.Sha, + ) + + d.Start(ctx) + + go func() { + err := <-d.Errors() + FatalErrorCheck(err) + }() + + go func() { + for state := range d.Stats() { + stateFunc(fileName, state.TotalSize, state.Downloaded, state.Percent) + if state.Completed { + done <- struct{}{} + close(done) + + return + } + } + }() + + <-done +} + +func (i *Importer) Cleanup() error { + return os.RemoveAll(i.tempDir) +} + +func (i *Importer) ExtractAndStoreFiles() error { + zipPath := filepath.Join(i.tempDir, i.dataFileName) + r, err := zip.OpenReader(zipPath) + if err != nil { + return fmt.Errorf("failed to open zip file: %w", err) + } + defer func() { + _ = r.Close() + }() + + for _, f := range r.File { + if err := i.extractAndWriteFile(f); err != nil { + return err + } + } + + return nil +} + +func (i *Importer) extractAndWriteFile(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return fmt.Errorf("failed to open file in zip archive: %w", err) + } + defer func() { + _ = rc.Close() + }() + + fPath, err := util.SanitizeArchivePath(i.tempDir, f.Name) + if err != nil { + return fmt.Errorf("failed to make archive path: %w", err) + } + + if f.FileInfo().IsDir() { + return util.Mkdir(fPath) + } + + if err := util.Mkdir(filepath.Dir(fPath)); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + + outFile, err := os.Create(fPath) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer func() { + _ = outFile.Close() + }() + + // Use a limited reader to prevent DoS attacks via decompression bomb + lr := &io.LimitedReader{R: rc, N: maxDecompressedSize} + written, err := io.Copy(outFile, lr) + if err != nil { + return fmt.Errorf("failed to copy file contents: %w", err) + } + + if written >= maxDecompressedSize { + return fmt.Errorf("file exceeds maximum decompressed size limit: %s", fPath) + } + + return nil +} + +func (i *Importer) MoveStore() error { + return util.MoveDirectory(filepath.Join(i.tempDir, "data"), i.storeDir) +} diff --git a/scripts/snapshot.py b/scripts/snapshot.py index cfcb70cd0..e84de29be 100644 --- a/scripts/snapshot.py +++ b/scripts/snapshot.py @@ -12,8 +12,8 @@ # - `--data_path`: This argument specifies the path to the Pactus data folder to create snapshots. # - Windows: `C:\Users\{user}\pactus\data` # - Linux or Mac: `/home/{user}/pactus/data` -# - `--compress`: This argument specifies the compression method based on your choice ['zip', 'tar'], -# default is zip. +# - `--compress`: This argument specifies the compression method based on your choice ['none', 'zip', 'tar'], +# with 'none' being without compression. # - `--retention`: This argument sets the number of snapshots to keep. # - `--snapshot_path`: This argument sets a custom path for snapshots, with the default being the current # working directory of the script. @@ -25,6 +25,7 @@ # sudo python3 snapshot.py --service_path /etc/systemd/system/pactus.service --data_path /home/{user}/pactus/data # --compress zip --retention 3 + import argparse import os import shutil @@ -72,7 +73,14 @@ def update_metadata_file(snapshot_path, snapshot_metadata): logging.info(f"Creating new metadata file '{metadata_file}'") metadata = [] - metadata.append(snapshot_metadata) + formatted_metadata = { + "name": snapshot_metadata["name"], + "created_at": snapshot_metadata["created_at"], + "compress": snapshot_metadata["compress"], + "data": snapshot_metadata["data"] + } + + metadata.append(formatted_metadata) with open(metadata_file, 'w') as f: json.dump(metadata, f, indent=4) @@ -92,6 +100,35 @@ def update_metadata_after_removal(snapshots_dir, removed_snapshots): with open(metadata_file, 'w') as f: json.dump(updated_metadata, f, indent=4) + @staticmethod + def create_snapshot_json(data_dir, snapshot_subdir): + files = [] + for root, _, filenames in os.walk(data_dir): + for filename in filenames: + file_path = os.path.join(root, filename) + rel_path = os.path.relpath(file_path, data_dir) + snapshot_rel_path = os.path.join(snapshot_subdir, rel_path).replace('\\', '/') + file_info = { + "name": filename, + "path": snapshot_rel_path, + "sha": Metadata.sha256(file_path) + } + files.append(file_info) + + return {"data": files} + + @staticmethod + def create_compressed_snapshot_json(compressed_file, rel_path): + compressed_file_size = os.path.getsize(compressed_file) + file_info = { + "name": os.path.basename(compressed_file), + "path": rel_path, + "sha": Metadata.sha256(compressed_file), + "size": compressed_file_size, + } + + return {"data": file_info} + def run_command(command): logging.info(f"Running command: {' '.join(command)}") @@ -160,39 +197,34 @@ def create_snapshot(self): logging.info(f"Creating snapshot directory '{snapshot_dir}'") os.makedirs(snapshot_dir, exist_ok=True) - data_dir = self.args.data_path - snapshot_metadata = {"name": timestamp_str, "created_at": get_current_time_iso(), - "compress": self.args.compress, "total_size": 0, "data": []} - - for root, _, files in os.walk(data_dir): - for file in files: - file_path = os.path.join(root, file) - file_name, file_ext = os.path.splitext(file) - compressed_file_name = f"{file_name}{file_ext}.{self.args.compress}" - compressed_file_path = os.path.join(snapshot_dir, compressed_file_name) - rel_path = os.path.relpath(compressed_file_path, self.args.snapshot_path) - - if rel_path.startswith('snapshots' + os.path.sep): - rel_path = rel_path[len('snapshots' + os.path.sep):] - - if self.args.compress == 'zip': - logging.info(f"Creating ZIP archive '{compressed_file_path}'") - with zipfile.ZipFile(compressed_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf: - zipf.write(file_path, file) - elif self.args.compress == 'tar': - logging.info(f"Creating TAR archive '{compressed_file_path}'") - subprocess.run(['tar', '-cvf', compressed_file_path, '-C', os.path.dirname(file_path), file]) - - compressed_file_size = os.path.getsize(compressed_file_path) - snapshot_metadata["total_size"] += compressed_file_size - - file_info = { - "name": file_name, - "path": rel_path, - "sha": Metadata.sha256(compressed_file_path), - "size": compressed_file_size - } - snapshot_metadata["data"].append(file_info) + data_dir = os.path.join(snapshot_dir, 'data') + if self.args.compress == 'none': + logging.info(f"Copying data from '{self.args.data_path}' to '{data_dir}'") + shutil.copytree(self.args.data_path, data_dir) + snapshot_metadata = Metadata.create_snapshot_json(data_dir, timestamp_str) + elif self.args.compress == 'zip': + zip_file = os.path.join(snapshot_dir, 'data.zip') + rel = os.path.relpath(zip_file, snapshot_dir) + meta_path = os.path.join(timestamp_str, rel) + logging.info(f"Creating ZIP archive '{zip_file}'") + with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, _, files in os.walk(self.args.data_path): + for file in files: + full_path = os.path.join(root, file) + rel_path = os.path.relpath(full_path, self.args.data_path) + zipf.write(full_path, os.path.join('data', rel_path)) + snapshot_metadata = Metadata.create_compressed_snapshot_json(zip_file, meta_path) + elif self.args.compress == 'tar': + tar_file = os.path.join(snapshot_dir, 'data.tar.gz') + rel = os.path.relpath(tar_file, snapshot_dir) + meta_path = os.path.join(timestamp_str, rel) + logging.info(f"Creating TAR.GZ archive '{tar_file}'") + subprocess.run(['tar', '-czvf', tar_file, '-C', self.args.data_path, '.']) + snapshot_metadata = Metadata.create_compressed_snapshot_json(tar_file, meta_path) + + snapshot_metadata["name"] = timestamp_str + snapshot_metadata["created_at"] = get_current_time_iso() + snapshot_metadata["compress"] = self.args.compress Metadata.update_metadata_file(self.args.snapshot_path, snapshot_metadata) @@ -268,7 +300,7 @@ def parse_args(): parser = argparse.ArgumentParser(description='Pactus Blockchain Snapshot Tool') parser.add_argument('--service_path', required=True, help='Path to pactus systemctl service') parser.add_argument('--data_path', default=default_data_path, help='Path to data directory') - parser.add_argument('--compress', choices=['zip', 'tar'], default='zip', help='Compression type') + parser.add_argument('--compress', choices=['none', 'zip', 'tar'], default='none', help='Compression type') parser.add_argument('--retention', type=int, default=3, help='Number of snapshots to retain') parser.add_argument('--snapshot_path', default=os.getcwd(), help='Path to store snapshots') diff --git a/util/io.go b/util/io.go index c32ce1975..9b257d997 100644 --- a/util/io.go +++ b/util/io.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "strings" ) func IsAbsPath(p string) bool { @@ -72,6 +73,7 @@ func TempFilePath() string { return filepath.Join(TempDirPath(), "file") } +// IsDirEmpty checks if a directory is empty. func IsDirEmpty(name string) bool { f, err := os.Open(name) if err != nil { @@ -88,6 +90,8 @@ func IsDirEmpty(name string) bool { return errors.Is(err, io.EOF) } +// IsDirNotExistsOrEmpty returns true if a directory does not exist or is empty. +// It checks if the path exists and, if so, whether the directory is empty. func IsDirNotExistsOrEmpty(name string) bool { if !PathExists(name) { return true @@ -196,3 +200,29 @@ func NewFixedReader(max int, buf []byte) *FixedReader { return &fr } + +// MoveDirectory moves a directory from srcDir to dstDir, including all its contents. +// If dstDir already exists and is not empty, it returns an error. +func MoveDirectory(srcDir, dstDir string) error { + if !IsDirNotExistsOrEmpty(dstDir) { + return fmt.Errorf("destination directory %s already exists", dstDir) + } + + if err := os.Rename(srcDir, dstDir); err != nil { + return fmt.Errorf("failed to move directory from %s to %s: %w", srcDir, dstDir, err) + } + + return nil +} + +// SanitizeArchivePath mitigates the "Zip Slip" vulnerability by sanitizing archive file paths. +// It ensures that the file path is contained within the specified base directory to prevent directory +// traversal attacks. For more details on the vulnerability, see https://snyk.io/research/zip-slip-vulnerability. +func SanitizeArchivePath(baseDir, archivePath string) (fullPath string, err error) { + fullPath = filepath.Join(baseDir, archivePath) + if strings.HasPrefix(fullPath, filepath.Clean(baseDir)) { + return fullPath, nil + } + + return "", fmt.Errorf("%s: %s", "content filepath is tainted", archivePath) +} diff --git a/util/io_test.go b/util/io_test.go index fec6ea052..aec40cd69 100644 --- a/util/io_test.go +++ b/util/io_test.go @@ -2,7 +2,9 @@ package util import ( "fmt" + "os" "os/exec" + "path/filepath" "runtime" "strconv" "testing" @@ -92,3 +94,83 @@ func TestIsValidPath(t *testing.T) { assert.True(t, IsValidDirPath("/tmp")) assert.True(t, IsValidDirPath("/tmp/pactus")) } + +func TestMoveDirectory(t *testing.T) { + // Create temporary directories + srcDir := TempDirPath() + dstDir := TempDirPath() + defer func() { _ = os.RemoveAll(srcDir) }() + defer func() { _ = os.RemoveAll(dstDir) }() + + // Create a subdirectory in the source directory + subDir := filepath.Join(srcDir, "subdir") + err := Mkdir(subDir) + assert.NoError(t, err) + + // Create multiple files in the subdirectory + files := []struct { + name string + content string + }{ + {"file1.txt", "content 1"}, + {"file2.txt", "content 2"}, + } + + for _, file := range files { + filePath := filepath.Join(subDir, file.name) + err = WriteFile(filePath, []byte(file.content)) + assert.NoError(t, err) + } + + // Move the directory + dstDirPath := filepath.Join(dstDir, "movedir") + err = MoveDirectory(srcDir, dstDirPath) + assert.NoError(t, err) + + // Assert the source directory no longer exists + assert.False(t, PathExists(srcDir)) + + // Assert the destination directory exists + assert.True(t, PathExists(dstDirPath)) + + // Verify that all files have been moved and their contents are correct + for _, file := range files { + movedFilePath := filepath.Join(dstDirPath, "subdir", file.name) + data, err := ReadFile(movedFilePath) + assert.NoError(t, err) + assert.Equal(t, file.content, string(data)) + } +} + +func TestSanitizeArchivePath(t *testing.T) { + if runtime.GOOS == "windows" { + return + } + + baseDir := "/safe/directory" + + tests := []struct { + name string + inputPath string + expected string + expectErr bool + }{ + {"Valid path", "file.txt", "/safe/directory/file.txt", false}, + {"Valid path in subdirectory", "subdir/file.txt", "/safe/directory/subdir/file.txt", false}, + {"Path with parent directory traversal", "../outside/file.txt", "", true}, + {"Absolute path outside base directory", "/etc/passwd", "/safe/directory/etc/passwd", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := SanitizeArchivePath(baseDir, tt.inputPath) + if tt.expectErr { + assert.Error(t, err, "Expected error but got none") + assert.Empty(t, result, "Expected empty result due to error") + } else { + assert.NoError(t, err, "Unexpected error occurred") + assert.Equal(t, tt.expected, result, "Sanitized path did not match expected") + } + }) + } +} diff --git a/util/utils.go b/util/utils.go index c84c5bc88..94ec20bd1 100644 --- a/util/utils.go +++ b/util/utils.go @@ -109,8 +109,8 @@ func IsFlagSet[T constraints.Integer](flags, mask T) bool { // OS2IP converts an octet string to a nonnegative integer. // OS2IP: https://datatracker.ietf.org/doc/html/rfc8017#section-4.2 -func OS2IP(os []byte) *big.Int { - return new(big.Int).SetBytes(os) +func OS2IP(x []byte) *big.Int { + return new(big.Int).SetBytes(x) } // I2OSP converts a nonnegative integer to an octet string of a specified length.