Skip to content

Commit

Permalink
feat: add GetAccountByNumber API to get account by number (#511)
Browse files Browse the repository at this point in the history
* feat: add GetAccountByNumber API to get account by number

Fixed #505

* fix: testing stage title and request number value

* fix: mocking test base on validator by number

* fix: remove grpc reflection

* fix: replace RLock and RUnlock instead Lock and Unlock

* fix: replace rand.Int31 with RandInt32 for rand number

* fix: add unimplemented GetAccountByNumber for grpc server register

* fix: lint error unnecessary leading newline (whitespace)

* fix: lint error lll

* fix: lint error File is not goimports -ed
  • Loading branch information
Ja7ad authored Jun 15, 2023
1 parent fd9e749 commit f550c79
Show file tree
Hide file tree
Showing 34 changed files with 1,619 additions and 528 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.19

require (
github.com/fxamacker/cbor/v2 v2.4.0
github.com/google/btree v1.1.2
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
Expand Down
1 change: 1 addition & 0 deletions state/facade.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Facade interface {
BlockHash(height uint32) hash.Hash
BlockHeight(hash hash.Hash) uint32
AccountByAddress(addr crypto.Address) *account.Account
AccountByNumber(number int32) *account.Account
ValidatorByAddress(addr crypto.Address) *validator.Validator
ValidatorByNumber(number int32) *validator.Validator
Params() param.Params
Expand Down
6 changes: 6 additions & 0 deletions state/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ func (m *MockState) AccountByAddress(addr crypto.Address) *account.Account {
a, _ := m.TestStore.Account(addr)
return a
}

func (m *MockState) AccountByNumber(number int32) *account.Account {
a, _ := m.TestStore.AccountByNumber(number)
return a
}

func (m *MockState) ValidatorByAddress(addr crypto.Address) *validator.Validator {
v, _ := m.TestStore.Validator(addr)
return v
Expand Down
8 changes: 8 additions & 0 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,14 @@ func (st *state) AccountByAddress(addr crypto.Address) *account.Account {
return acc
}

func (st *state) AccountByNumber(number int32) *account.Account {
acc, err := st.store.AccountByNumber(number)
if err != nil {
st.logger.Trace("error on retrieving account", "err", err)
}
return acc
}

func (st *state) ValidatorByAddress(addr crypto.Address) *validator.Validator {
val, err := st.store.Validator(addr)
if err != nil {
Expand Down
24 changes: 20 additions & 4 deletions store/account.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package store

import (
"fmt"

"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/types/account"
"github.com/pactus-project/pactus/util/logger"
Expand All @@ -9,18 +11,21 @@ import (
)

type accountStore struct {
db *leveldb.DB
total int32
db *leveldb.DB
numberMap map[int32]*account.Account
total int32
}

func accountKey(addr crypto.Address) []byte { return append(accountPrefix, addr.Bytes()...) }

func newAccountStore(db *leveldb.DB) *accountStore {
as := &accountStore{
db: db,
db: db,
numberMap: make(map[int32]*account.Account),
}
total := int32(0)
as.iterateAccounts(func(_ crypto.Address, _ *account.Account) bool {
as.iterateAccounts(func(_ crypto.Address, acc *account.Account) bool {
as.numberMap[acc.Number()] = acc
total++
return false
})
Expand Down Expand Up @@ -51,6 +56,15 @@ func (as *accountStore) account(addr crypto.Address) (*account.Account, error) {
return acc, nil
}

func (as *accountStore) accountByNumber(number int32) (*account.Account, error) {
val, ok := as.numberMap[number]
if ok {
return val, nil
}

return nil, fmt.Errorf("account not found")
}

func (as *accountStore) iterateAccounts(consumer func(crypto.Address, *account.Account) (stop bool)) {
r := util.BytesPrefix(accountPrefix)
iter := as.db.NewIterator(r, nil)
Expand Down Expand Up @@ -82,5 +96,7 @@ func (as *accountStore) updateAccount(batch *leveldb.Batch, addr crypto.Address,
if !as.hasAccount(addr) {
as.total++
}
as.numberMap[acc.Number()] = acc

batch.Put(accountKey(addr), data)
}
36 changes: 36 additions & 0 deletions store/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/pactus-project/pactus/types/account"
"github.com/pactus-project/pactus/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAccountCounter(t *testing.T) {
Expand Down Expand Up @@ -53,3 +54,38 @@ func TestAccountBatchSaving(t *testing.T) {
assert.Equal(t, store.TotalAccounts(), int32(100))
})
}

func TestAccountByNumber(t *testing.T) {
setup(t)

t.Run("Add some accounts", func(t *testing.T) {
for i := 0; i < 10; i++ {
val, signer := account.GenerateTestAccount(int32(i))
tStore.UpdateAccount(signer.Address(), val)
}
assert.NoError(t, tStore.WriteBatch())

v, err := tStore.AccountByNumber(5)
assert.NoError(t, err)
require.NotNil(t, v)
assert.Equal(t, v.Number(), int32(5))

v, err = tStore.AccountByNumber(11)
assert.Error(t, err)
assert.Nil(t, v)
})

t.Run("Reopen the store", func(t *testing.T) {
tStore.Close()
store, _ := NewStore(tStore.config, 21)

v, err := store.AccountByNumber(5)
assert.NoError(t, err)
require.NotNil(t, v)
assert.Equal(t, v.Number(), int32(5))

v, err = tStore.AccountByNumber(11)
assert.Error(t, err)
assert.Nil(t, v)
})
}
1 change: 1 addition & 0 deletions store/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type Reader interface {
Transaction(id tx.ID) (*StoredTx, error)
HasAccount(crypto.Address) bool
Account(addr crypto.Address) (*account.Account, error)
AccountByNumber(number int32) (*account.Account, error)
TotalAccounts() int32
HasValidator(crypto.Address) bool
Validator(addr crypto.Address) (*validator.Validator, error)
Expand Down
11 changes: 11 additions & 0 deletions store/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,20 @@ func (m *MockStore) Account(addr crypto.Address) (*account.Account, error) {
}
return nil, fmt.Errorf("not found")
}

func (m *MockStore) AccountByNumber(number int32) (*account.Account, error) {
for _, v := range m.Accounts {
if v.Number() == number {
return &v, nil
}
}
return nil, fmt.Errorf("not found")
}

func (m *MockStore) UpdateAccount(addr crypto.Address, acc *account.Account) {
m.Accounts[addr] = *acc
}

func (m *MockStore) TotalAccounts() int32 {
return int32(len(m.Accounts))
}
Expand Down
7 changes: 7 additions & 0 deletions store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ func (s *store) Account(addr crypto.Address) (*account.Account, error) {
return s.accountStore.account(addr)
}

func (s *store) AccountByNumber(number int32) (*account.Account, error) {
s.lk.RLock()
defer s.lk.RUnlock()

return s.accountStore.accountByNumber(number)
}

func (s *store) TotalAccounts() int32 {
s.lk.Lock()
defer s.lk.Unlock()
Expand Down
6 changes: 6 additions & 0 deletions wallet/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

type blockchainServer struct{}

type transactionServer struct{}

var tBlockchainInfoResponse *pactus.GetBlockchainInfoResponse
Expand Down Expand Up @@ -48,6 +49,11 @@ func (s *blockchainServer) GetAccount(_ context.Context,
return nil, fmt.Errorf("unknown request")
}

func (s *blockchainServer) GetAccountByNumber(_ context.Context,
_ *pactus.GetAccountByNumberRequest) (*pactus.GetAccountResponse, error) {
return nil, nil
}

func (s *blockchainServer) GetValidatorByNumber(_ context.Context,
_ *pactus.GetValidatorByNumberRequest) (*pactus.GetValidatorResponse, error) {
return nil, nil
Expand Down
12 changes: 12 additions & 0 deletions www/grpc/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ func (s *blockchainServer) GetAccount(_ context.Context,
return res, nil
}

func (s *blockchainServer) GetAccountByNumber(_ context.Context,
req *pactus.GetAccountByNumberRequest) (*pactus.GetAccountResponse, error) {
acc := s.state.AccountByNumber(req.Number)
if acc == nil {
return nil, status.Errorf(codes.InvalidArgument, "account not found")
}

return &pactus.GetAccountResponse{
Account: accountToProto(acc),
}, nil
}

func (s *blockchainServer) GetValidatorByNumber(_ context.Context,
req *pactus.GetValidatorByNumberRequest) (*pactus.GetValidatorResponse, error) {
val := s.state.ValidatorByNumber(req.Number)
Expand Down
35 changes: 35 additions & 0 deletions www/grpc/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/hash"
"github.com/pactus-project/pactus/types/vote"
"github.com/pactus-project/pactus/util"
pactus "github.com/pactus-project/pactus/www/grpc/gen/go"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -179,6 +180,40 @@ func TestGetAccount(t *testing.T) {
})
assert.Nil(t, conn.Close(), "Error closing connection")
}

func TestGetAccountByNumber(t *testing.T) {
conn, client := testBlockchainClient(t)
acc, _ := tMockState.TestStore.AddTestAccount()

t.Run("Should return nil value due to invalid number ", func(t *testing.T) {
res, err := client.GetAccountByNumber(tCtx,
&pactus.GetAccountByNumberRequest{Number: -1})

assert.Error(t, err)
assert.Nil(t, res)
})

t.Run("Should return nil for non existing account ", func(t *testing.T) {
res, err := client.GetAccountByNumber(tCtx,
&pactus.GetAccountByNumberRequest{Number: util.RandInt32(0)})

assert.Error(t, err)
assert.Nil(t, res)
})

t.Run("Should return account details", func(t *testing.T) {
res, err := client.GetAccountByNumber(tCtx,
&pactus.GetAccountByNumberRequest{Number: acc.Number()})

assert.Nil(t, err)
assert.NotNil(t, res)
assert.Equal(t, res.Account.Balance, acc.Balance())
assert.Equal(t, res.Account.Number, acc.Number())
assert.Equal(t, res.Account.Sequence, acc.Sequence())
})
assert.Nil(t, conn.Close(), "Error closing connection")
}

func TestGetValidator(t *testing.T) {
conn, client := testBlockchainClient(t)
val1 := tMockState.TestStore.AddTestValidator()
Expand Down
51 changes: 51 additions & 0 deletions www/grpc/gen/dart/blockchain.pb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,53 @@ class GetAccountRequest extends $pb.GeneratedMessage {
void clearAddress() => clearField(1);
}

class GetAccountByNumberRequest extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GetAccountByNumberRequest', package: const $pb.PackageName(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'pactus'), createEmptyInstance: create)
..a<$core.int>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'number', $pb.PbFieldType.O3)
..hasRequiredFields = false
;

GetAccountByNumberRequest._() : super();
factory GetAccountByNumberRequest({
$core.int? number,
}) {
final _result = create();
if (number != null) {
_result.number = number;
}
return _result;
}
factory GetAccountByNumberRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory GetAccountByNumberRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
GetAccountByNumberRequest clone() => GetAccountByNumberRequest()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
GetAccountByNumberRequest copyWith(void Function(GetAccountByNumberRequest) updates) => super.copyWith((message) => updates(message as GetAccountByNumberRequest)) as GetAccountByNumberRequest; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GetAccountByNumberRequest create() => GetAccountByNumberRequest._();
GetAccountByNumberRequest createEmptyInstance() => create();
static $pb.PbList<GetAccountByNumberRequest> createRepeated() => $pb.PbList<GetAccountByNumberRequest>();
@$core.pragma('dart2js:noInline')
static GetAccountByNumberRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GetAccountByNumberRequest>(create);
static GetAccountByNumberRequest? _defaultInstance;

@$pb.TagNumber(1)
$core.int get number => $_getIZ(0);
@$pb.TagNumber(1)
set number($core.int v) { $_setSignedInt32(0, v); }
@$pb.TagNumber(1)
$core.bool hasNumber() => $_has(0);
@$pb.TagNumber(1)
void clearNumber() => clearField(1);
}

class GetAccountResponse extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GetAccountResponse', package: const $pb.PackageName(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'pactus'), createEmptyInstance: create)
..aOM<AccountInfo>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'account', subBuilder: AccountInfo.create)
Expand Down Expand Up @@ -1612,6 +1659,10 @@ class BlockchainApi {
var emptyResponse = GetAccountResponse();
return _client.invoke<GetAccountResponse>(ctx, 'Blockchain', 'GetAccount', request, emptyResponse);
}
$async.Future<GetAccountResponse> getAccountByNumber($pb.ClientContext? ctx, GetAccountByNumberRequest request) {
var emptyResponse = GetAccountResponse();
return _client.invoke<GetAccountResponse>(ctx, 'Blockchain', 'GetAccountByNumber', request, emptyResponse);
}
$async.Future<GetValidatorResponse> getValidator($pb.ClientContext? ctx, GetValidatorRequest request) {
var emptyResponse = GetValidatorResponse();
return _client.invoke<GetValidatorResponse>(ctx, 'Blockchain', 'GetValidator', request, emptyResponse);
Expand Down
14 changes: 13 additions & 1 deletion www/grpc/gen/dart/blockchain.pbjson.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ const GetAccountRequest$json = const {

/// Descriptor for `GetAccountRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List getAccountRequestDescriptor = $convert.base64Decode('ChFHZXRBY2NvdW50UmVxdWVzdBIYCgdhZGRyZXNzGAEgASgJUgdhZGRyZXNz');
@$core.Deprecated('Use getAccountByNumberRequestDescriptor instead')
const GetAccountByNumberRequest$json = const {
'1': 'GetAccountByNumberRequest',
'2': const [
const {'1': 'number', '3': 1, '4': 1, '5': 5, '10': 'number'},
],
};

/// Descriptor for `GetAccountByNumberRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List getAccountByNumberRequestDescriptor = $convert.base64Decode('ChlHZXRBY2NvdW50QnlOdW1iZXJSZXF1ZXN0EhYKBm51bWJlchgBIAEoBVIGbnVtYmVy');
@$core.Deprecated('Use getAccountResponseDescriptor instead')
const GetAccountResponse$json = const {
'1': 'GetAccountResponse',
Expand Down Expand Up @@ -306,6 +316,7 @@ const $core.Map<$core.String, $core.dynamic> BlockchainServiceBase$json = const
const {'1': 'GetBlockchainInfo', '2': '.pactus.GetBlockchainInfoRequest', '3': '.pactus.GetBlockchainInfoResponse'},
const {'1': 'GetConsensusInfo', '2': '.pactus.GetConsensusInfoRequest', '3': '.pactus.GetConsensusInfoResponse'},
const {'1': 'GetAccount', '2': '.pactus.GetAccountRequest', '3': '.pactus.GetAccountResponse'},
const {'1': 'GetAccountByNumber', '2': '.pactus.GetAccountByNumberRequest', '3': '.pactus.GetAccountResponse'},
const {'1': 'GetValidator', '2': '.pactus.GetValidatorRequest', '3': '.pactus.GetValidatorResponse'},
const {'1': 'GetValidatorByNumber', '2': '.pactus.GetValidatorByNumberRequest', '3': '.pactus.GetValidatorResponse'},
const {'1': 'GetValidators', '2': '.pactus.GetValidatorsRequest', '3': '.pactus.GetValidatorsResponse'},
Expand Down Expand Up @@ -338,6 +349,7 @@ const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> Blockchain
'.pactus.GetAccountRequest': GetAccountRequest$json,
'.pactus.GetAccountResponse': GetAccountResponse$json,
'.pactus.AccountInfo': AccountInfo$json,
'.pactus.GetAccountByNumberRequest': GetAccountByNumberRequest$json,
'.pactus.GetValidatorRequest': GetValidatorRequest$json,
'.pactus.GetValidatorResponse': GetValidatorResponse$json,
'.pactus.GetValidatorByNumberRequest': GetValidatorByNumberRequest$json,
Expand All @@ -346,4 +358,4 @@ const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> Blockchain
};

/// Descriptor for `Blockchain`. Decode as a `google.protobuf.ServiceDescriptorProto`.
final $typed_data.Uint8List blockchainServiceDescriptor = $convert.base64Decode('CgpCbG9ja2NoYWluEj0KCEdldEJsb2NrEhcucGFjdHVzLkdldEJsb2NrUmVxdWVzdBoYLnBhY3R1cy5HZXRCbG9ja1Jlc3BvbnNlEkkKDEdldEJsb2NrSGFzaBIbLnBhY3R1cy5HZXRCbG9ja0hhc2hSZXF1ZXN0GhwucGFjdHVzLkdldEJsb2NrSGFzaFJlc3BvbnNlEk8KDkdldEJsb2NrSGVpZ2h0Eh0ucGFjdHVzLkdldEJsb2NrSGVpZ2h0UmVxdWVzdBoeLnBhY3R1cy5HZXRCbG9ja0hlaWdodFJlc3BvbnNlElgKEUdldEJsb2NrY2hhaW5JbmZvEiAucGFjdHVzLkdldEJsb2NrY2hhaW5JbmZvUmVxdWVzdBohLnBhY3R1cy5HZXRCbG9ja2NoYWluSW5mb1Jlc3BvbnNlElUKEEdldENvbnNlbnN1c0luZm8SHy5wYWN0dXMuR2V0Q29uc2Vuc3VzSW5mb1JlcXVlc3QaIC5wYWN0dXMuR2V0Q29uc2Vuc3VzSW5mb1Jlc3BvbnNlEkMKCkdldEFjY291bnQSGS5wYWN0dXMuR2V0QWNjb3VudFJlcXVlc3QaGi5wYWN0dXMuR2V0QWNjb3VudFJlc3BvbnNlEkkKDEdldFZhbGlkYXRvchIbLnBhY3R1cy5HZXRWYWxpZGF0b3JSZXF1ZXN0GhwucGFjdHVzLkdldFZhbGlkYXRvclJlc3BvbnNlElkKFEdldFZhbGlkYXRvckJ5TnVtYmVyEiMucGFjdHVzLkdldFZhbGlkYXRvckJ5TnVtYmVyUmVxdWVzdBocLnBhY3R1cy5HZXRWYWxpZGF0b3JSZXNwb25zZRJMCg1HZXRWYWxpZGF0b3JzEhwucGFjdHVzLkdldFZhbGlkYXRvcnNSZXF1ZXN0Gh0ucGFjdHVzLkdldFZhbGlkYXRvcnNSZXNwb25zZQ==');
final $typed_data.Uint8List blockchainServiceDescriptor = $convert.base64Decode('CgpCbG9ja2NoYWluEj0KCEdldEJsb2NrEhcucGFjdHVzLkdldEJsb2NrUmVxdWVzdBoYLnBhY3R1cy5HZXRCbG9ja1Jlc3BvbnNlEkkKDEdldEJsb2NrSGFzaBIbLnBhY3R1cy5HZXRCbG9ja0hhc2hSZXF1ZXN0GhwucGFjdHVzLkdldEJsb2NrSGFzaFJlc3BvbnNlEk8KDkdldEJsb2NrSGVpZ2h0Eh0ucGFjdHVzLkdldEJsb2NrSGVpZ2h0UmVxdWVzdBoeLnBhY3R1cy5HZXRCbG9ja0hlaWdodFJlc3BvbnNlElgKEUdldEJsb2NrY2hhaW5JbmZvEiAucGFjdHVzLkdldEJsb2NrY2hhaW5JbmZvUmVxdWVzdBohLnBhY3R1cy5HZXRCbG9ja2NoYWluSW5mb1Jlc3BvbnNlElUKEEdldENvbnNlbnN1c0luZm8SHy5wYWN0dXMuR2V0Q29uc2Vuc3VzSW5mb1JlcXVlc3QaIC5wYWN0dXMuR2V0Q29uc2Vuc3VzSW5mb1Jlc3BvbnNlEkMKCkdldEFjY291bnQSGS5wYWN0dXMuR2V0QWNjb3VudFJlcXVlc3QaGi5wYWN0dXMuR2V0QWNjb3VudFJlc3BvbnNlElMKEkdldEFjY291bnRCeU51bWJlchIhLnBhY3R1cy5HZXRBY2NvdW50QnlOdW1iZXJSZXF1ZXN0GhoucGFjdHVzLkdldEFjY291bnRSZXNwb25zZRJJCgxHZXRWYWxpZGF0b3ISGy5wYWN0dXMuR2V0VmFsaWRhdG9yUmVxdWVzdBocLnBhY3R1cy5HZXRWYWxpZGF0b3JSZXNwb25zZRJZChRHZXRWYWxpZGF0b3JCeU51bWJlchIjLnBhY3R1cy5HZXRWYWxpZGF0b3JCeU51bWJlclJlcXVlc3QaHC5wYWN0dXMuR2V0VmFsaWRhdG9yUmVzcG9uc2USTAoNR2V0VmFsaWRhdG9ycxIcLnBhY3R1cy5HZXRWYWxpZGF0b3JzUmVxdWVzdBodLnBhY3R1cy5HZXRWYWxpZGF0b3JzUmVzcG9uc2U=');
Loading

0 comments on commit f550c79

Please sign in to comment.