diff --git a/Makefile b/Makefile index d50c2db78b..9ddb11b91d 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ BINANCE_TGORACLE_DIR=$(GOPATH)/src/$(PKG_BINANCE_TGORACLE) # specific commit. GO_LOOM_GIT_REV = HEAD # Specifies the loomnetwork/transfer-gateway branch/revision to use. -TG_GIT_REV = HEAD +TG_GIT_REV = export-getcontract-mapping # loomnetwork/go-ethereum loomchain branch ETHEREUM_GIT_REV = 6128fa1a8c767035d3da6ef0c27ebb7778ce3713 # use go-plugin we get 'timeout waiting for connection info' error diff --git a/rpc/instrumenting.go b/rpc/instrumenting.go index 94cd69df0e..c10ff3ecf2 100755 --- a/rpc/instrumenting.go +++ b/rpc/instrumenting.go @@ -150,6 +150,17 @@ func (m InstrumentingMiddleware) GetCanonicalTxHash(block, txIndex uint64, evmTx return resp, err } +func (m InstrumentingMiddleware) GetAccountBalances(contracts []string) (resp *AccountsBalanceResponse, err error) { + defer func(begin time.Time) { + lvs := []string{"method", "GetAccountBalances", "error", fmt.Sprint(err != nil)} + m.requestCount.With(lvs...).Add(1) + m.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + resp, err = m.next.GetAccountBalances(contracts) + return resp, err +} + func (m InstrumentingMiddleware) GetEvmCode(contract string) (resp []byte, err error) { defer func(begin time.Time) { lvs := []string{"method", "GetEvmCode", "error", fmt.Sprint(err != nil)} diff --git a/rpc/query_server.go b/rpc/query_server.go index bdcce58b6e..61f7493114 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -1323,3 +1323,15 @@ func getTxByTendermintHash( ) return txObj, err } + +type AccountsBalanceResponse struct { + Accounts map[string]map[string]string +} + +type AccountMappingBalance struct { + DAppAddress loom.Address + ContractsBalance []struct { + Address loom.Address + Balance string + } +} diff --git a/rpc/query_server_gateway.go b/rpc/query_server_gateway.go new file mode 100644 index 0000000000..79662d3e6e --- /dev/null +++ b/rpc/query_server_gateway.go @@ -0,0 +1,132 @@ +// +build gateway + +package rpc + +import ( + "fmt" + + "github.com/loomnetwork/go-loom" + ctypes "github.com/loomnetwork/go-loom/builtin/types/coin" + "github.com/loomnetwork/go-loom/plugin/contractpb" + "github.com/loomnetwork/loomchain/builtin/plugins/ethcoin" + "github.com/pkg/errors" + + amtypes "github.com/loomnetwork/go-loom/builtin/types/address_mapper" + tgtypes "github.com/loomnetwork/go-loom/builtin/types/transfer_gateway" + glcommon "github.com/loomnetwork/go-loom/common" + gtypes "github.com/loomnetwork/go-loom/types" + am "github.com/loomnetwork/loomchain/builtin/plugins/address_mapper" + "github.com/loomnetwork/loomchain/builtin/plugins/coin" + "github.com/loomnetwork/transfer-gateway/builtin/plugins/gateway" +) + +func (s *QueryServer) GetAccountBalances(contracts []string) (*AccountsBalanceResponse, error) { + snapshot := s.StateProvider.ReadOnlyState() + defer snapshot.Release() + + addrMapperCtx, err := s.createAddressMapperCtx(s.StateProvider.ReadOnlyState()) + if err != nil { + return nil, err + } + var resp *amtypes.AddressMapperListMappingResponse + mapper := &am.AddressMapper{} + resp, err = mapper.ListMapping(addrMapperCtx, nil) + if err != nil { + return nil, err + } + + if len(resp.Mappings) == 0 { + return nil, errors.Errorf("Empty address mapping list") + } + + ethcoinCtx, err := s.createStaticContractCtx(snapshot, "ethcoin") + if err != nil { + return nil, err + } + + loomCoinCtx, err := s.createStaticContractCtx(snapshot, "coin") + if err != nil { + return nil, err + } + + gatewayCtx, err := s.createStaticContractCtx(snapshot, "gateway") + if err != nil { + return nil, err + } + + accounts := make(map[string]map[string]string, len(resp.Mappings)) + for _, mp := range resp.Mappings { + var localAddr, foreignAddr loom.Address + if mp.From.ChainId == s.ChainID { + localAddr = loom.UnmarshalAddressPB(mp.From) + foreignAddr = loom.UnmarshalAddressPB(mp.To) + } else { + localAddr = loom.UnmarshalAddressPB(mp.To) + foreignAddr = loom.UnmarshalAddressPB(mp.From) + } + fmt.Println("from -> ", mp.From, " - ", mp.To) + account := make(map[string]string, len(resp.Mappings)) + for _, contract := range contracts { + switch contract { + case "eth": + ethBal, err := getEthBalance(ethcoinCtx, localAddr) + if err != nil { + return nil, err + } + account[contract] = ethBal.String() + case "loom": + loomBal, err := getLoomBalance(loomCoinCtx, localAddr) + if err != nil { + return nil, err + } + account[contract] = loomBal.String() + default: + erc20ContractAddr, err := getMappedContractAddress(gatewayCtx, loom.MustParseAddress(contract)) + if err != nil { + return nil, err + } + erc20Ctx := gateway.NewERC20StaticContext(gatewayCtx, loom.UnmarshalAddressPB(erc20ContractAddr)) + erc20Bal, err := gateway.BalanceOf(erc20Ctx, localAddr) + if err != nil { + return nil, err + } + account[contract] = erc20Bal.String() + } + } + accounts[foreignAddr.String()] = account + } + + return &AccountsBalanceResponse{Accounts: accounts}, nil +} + +func getEthBalance(ctx contractpb.StaticContext, address loom.Address) (*glcommon.BigUInt, error) { + amount, err := ethcoin.BalanceOf(ctx, address) + if err != nil { + return nil, errors.Wrapf(err, "error getting balance of %s", address.Local.String()) + } + if amount == nil { + return glcommon.BigZero(), nil + } + return amount, nil +} + +func getLoomBalance(ctx contractpb.StaticContext, address loom.Address) (*glcommon.BigUInt, error) { + lc := &coin.Coin{} + amount, err := lc.BalanceOf(ctx, &ctypes.BalanceOfRequest{Owner: address.MarshalPB()}) + if err != nil { + return nil, errors.Wrapf(err, "error getting balance of %s", address.Local.String()) + } + if amount == nil { + return glcommon.BigZero(), nil + } + return &amount.Balance.Value, nil +} + +func getMappedContractAddress(ctx contractpb.StaticContext, address loom.Address) (*gtypes.Address, error) { + var resp *tgtypes.TransferGatewayGetContractMappingResponse + resp, err := gateway.GetContractMapping(ctx, &tgtypes.TransferGatewayGetContractMappingRequest{From: address.MarshalPB()}) + if err != nil { + return nil, err + } + return resp.MappedAddress, nil +} diff --git a/rpc/query_server_no_gateway.go b/rpc/query_server_no_gateway.go new file mode 100644 index 0000000000..beb0cbcd17 --- /dev/null +++ b/rpc/query_server_no_gateway.go @@ -0,0 +1,11 @@ +// +build !gateway + +package rpc + +import ( + "github.com/pkg/errors" +) + +func (s *QueryServer) GetAccountBalances(contract []string) (*AccountsBalanceResponse, error) { + return nil, errors.New("GetAccountBalances not implemented in this build") +} diff --git a/rpc/query_service.go b/rpc/query_service.go index cb22fe430b..63e5c933d8 100755 --- a/rpc/query_service.go +++ b/rpc/query_service.go @@ -65,6 +65,7 @@ type QueryService interface { GetContractRecord(contractAddr string) (*types.ContractRecordResponse, error) DPOSTotalStaked() (*DPOSTotalStakedResponse, error) GetCanonicalTxHash(block, txIndex uint64, evmTxHash eth.Data) (eth.Data, error) + GetAccountBalances(contracts []string) (*AccountsBalanceResponse, error) // deprecated function EvmTxReceipt(txHash []byte) ([]byte, error) @@ -213,7 +214,7 @@ func MakeEthQueryServiceHandler(logger log.TMLogger, hub *Hub, routes map[string } // MakeUnsafeQueryServiceHandler returns a http handler for unsafe RPC routes -func MakeUnsafeQueryServiceHandler(logger log.TMLogger) http.Handler { +func MakeUnsafeQueryServiceHandler(svc QueryService, logger log.TMLogger) http.Handler { codec := amino.NewCodec() mux := http.NewServeMux() routes := map[string]*rpcserver.RPCFunc{} @@ -226,6 +227,8 @@ func MakeUnsafeQueryServiceHandler(logger log.TMLogger) http.Handler { routes["unsafe_stop_cpu_profiler"] = rpcserver.NewRPCFunc(rpccore.UnsafeStopCPUProfiler, "") routes["unsafe_write_heap_profile"] = rpcserver.NewRPCFunc(rpccore.UnsafeWriteHeapProfile, "filename") + routes["account_balances"] = rpcserver.NewRPCFunc(svc.GetAccountBalances, "contracts") + rpcserver.RegisterRPCFuncs(mux, routes, codec, logger) return mux } diff --git a/rpc/rpc_server.go b/rpc/rpc_server.go index 12421f71bc..941e110cb9 100755 --- a/rpc/rpc_server.go +++ b/rpc/rpc_server.go @@ -94,7 +94,7 @@ func RPCServer( if enableUnsafeRPC { unsafeLogger := logger.With("interface", "unsafe") - unsafeHandler := MakeUnsafeQueryServiceHandler(unsafeLogger) + unsafeHandler := MakeUnsafeQueryServiceHandler(qsvc, unsafeLogger) unsafeListener, err := rpcserver.Listen( unsafeRPCBindAddress, rpcserver.Config{MaxOpenConnections: 0},