Extra Challenges
Challenge 1: Limit Input
It seems the nameservice will let you set any name length you want. Add a validation check in SetServiceName
to ensure the name is less than 32 characters long.
Hint #1
The SetServiceName
in the msg_server.go looks like an interesting place to start. It should return an error if the name is too long.
Solution
If a user attempts to submit a name longer than 32 characters, it will return an error that is not allowed.
// SetServiceName implements types.MsgServer.
func (ms msgServer) SetServiceName(ctx context.Context, msg *types.MsgSetServiceName) (*types.MsgSetServiceNameResponse, error) {
if len(msg.Name) > 32 {
return nil, fmt.Errorf("name cannot be longer than 32 characters")
}
if err := ms.k.NameMapping.Set(ctx, msg.Sender, msg.Name); err != nil {
return nil, err
}
return &types.MsgSetServiceNameResponse{}, nil
}
Challenge 2: Resolve Wallet From Name
Currently the nameservice only allows you to resolve a name given a wallet. If someone has a name they should be able to resolve the wallet address. Add a new query to the query_server
and autocli client to resolve a wallet address from a name.
This challenge is signinicantly harder and will some previous Go programming knowledge with iterators. You can also just copy the solutions.
Hint #1
Create a new query.proto for ResolveWallet that takes in a name string
Solution #1
// Query provides defines the gRPC querier service.
service Query {
// Params queries all parameters of the module.
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/nameservice/v1/params";
}
// ResolveName allows a user to resolve the name of an account.
rpc ResolveName(QueryResolveNameRequest) returns (QueryResolveNameResponse) {
option (google.api.http).get = "/nameservice/v1/name/{wallet}";
}
// ResolveWallet allows a user to resolve the wallet of a name.
rpc ResolveWallet(QueryResolveWalletRequest) returns (QueryResolveWalletResponse) {
option (google.api.http).get = "/nameservice/v1/wallet/{name}";
}
}
message QueryResolveWalletRequest {
string name = 1;
}
message QueryResolveWalletResponse {
string wallet = 1;
}
make proto-gen
Hint #2
Iterate through the k.Keeper.NameMapping
, check the Value(). if it matches the name requested, return that wallet (Key)
Solution #2
// ResolveWallet implements types.QueryServer.
func (k Querier) ResolveWallet(goCtx context.Context, req *types.QueryResolveWalletRequest) (*types.QueryResolveWalletResponse, error) {
// create a way to iterate over all the name mappings.
iter, err := k.Keeper.NameMapping.Iterate(goCtx, nil)
if err != nil {
return nil, err
}
defer iter.Close()
for ; iter.Valid(); iter.Next() {
// get the value (name)
v, err := iter.Value()
if err != nil {
return nil, err
}
// if current name matches the requested name,
// return the wallet address for the name
if v == req.Name {
walletAddr, err := iter.Key()
if err != nil {
return nil, err
}
return &types.QueryResolveWalletResponse{
Wallet: walletAddr,
}, nil
}
}
return nil, fmt.Errorf("wallet not found for name %s", req.Name)
}
This is not the most efficient way to do this. If you would like, create a new WalletMapping collection that maps name->sender when SetServiceName
is called. This way you can resolve the wallet from the name in O(1) time (i.e. instant) instead of looping through all possible wallets.
Hint #3
Add the AutoCLI method to ResolveWallet
with the ProtoField
"name" to match the .proto file
Solution #3
func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
return &autocliv1.ModuleOptions{
Query: &autocliv1.ServiceCommandDescriptor{
Service: modulev1.Query_ServiceDesc.ServiceName,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "ResolveName",
Use: "resolve [wallet]",
Short: "Resolve the name of a wallet address",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
{ProtoField: "wallet"},
},
},
{
RpcMethod: "ResolveWallet",
Use: "wallet [name]",
Short: "Resolve the wallet address from a given name",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
{ProtoField: "name"},
},
},
{
RpcMethod: "Params",
Use: "params",
Short: "Query the current module parameters",
},
},
},
...
Then make install
and re-run the testnet to verify rolld q nameservice wallet <name>
returns the expected wallet address.