The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
woop/rosetta/services/network.go

258 lines
7.9 KiB

Rosetta Implementation - pt2 (Stage 3.2 of Node API Overhaul) (#3312) * [rosetta] Add server stop Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Make network naming consistent Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Correct common package name & add error enum Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Remove needless forward of network info to services * Implement /network/list Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Refactor errors & add operation statuses and types Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Implement NetworkOptions & update NetworkAPIService * Rename *_service.go files to remove the suffix * Update StartServers to use new operation types Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Impl NetworkStatus - Finish init impl of /network endpoint * Fix import structure for rosetta.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [test] Make explorer run as archival for localnet Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add unit tests * Force errors to remain the same with unit tests * Force operations to remain the same with unit tests * Ensure network checking works for all cases with unit tests Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add InvalidNetworkError and correct error codes Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add UnmarshalFromInterface for SubNetworkMetadata Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add network checking Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Nit fixes & add unit test for Peer Info * Make names consistent Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add BlockNotFoundError & TransactionNotFoundError Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Implement skeleton for block transactions Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add ReceiptNotFoundError Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add receipt to formatTransaction sig for contract fails Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add currency, ExpendGasOperation, & ContractCreationOperation * Add Error creator Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Init impl of plain transaction formatting Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Update network.go for new error constructor Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Implement stx formatter & refactor BlockTransaction * Updated todo comments & function formatting Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Impl Block & make currency non-ptr for easy copy with custom metadata Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix collect rewards amount on transaction fetch Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix block look-up edge case & add recovery middleware Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add bocks unit tests Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix checkPeerID unit test in network_test.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix staking tx amount for tx ops & update inline docs Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix lint Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Refactor getStakingOperations Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix undelegate value Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Nit - fix formatting for network.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [node] Move genesis allocation to core & remove unused ContractDeployerKey Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix precision error & add cx receipt hash on blk fetch * Add unit tests for supporting helper functions Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix fmt Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix cx receipt hashes for blocks * Print stack trace on panic recovery Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [node] Nit - fix comment for StopRosetta Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [node] Expose GetMaxPeerHeight Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add SyncStatus enum Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Nit - remove redundant 'service' name in services namespace Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>
4 years ago
package services
import (
"context"
"fmt"
"github.com/coinbase/rosetta-sdk-go/server"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/harmony-one/harmony/hmy"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/rosetta/common"
commonRPC "github.com/harmony-one/harmony/rpc/common"
"github.com/harmony-one/harmony/shard"
)
// NetworkAPI implements the server.NetworkAPIServicer interface.
type NetworkAPI struct {
hmy *hmy.Harmony
}
// NewNetworkAPI creates a new instance of a NetworkAPI.
func NewNetworkAPI(hmy *hmy.Harmony) server.NetworkAPIServicer {
return &NetworkAPI{
hmy: hmy,
}
}
// NetworkList implements the /network/list endpoint
// TODO (dm): Update Node API to support multiple shards...
func (s *NetworkAPI) NetworkList(
ctx context.Context, request *types.MetadataRequest,
) (*types.NetworkListResponse, *types.Error) {
network, err := common.GetNetwork(s.hmy.ShardID)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
return &types.NetworkListResponse{
NetworkIdentifiers: []*types.NetworkIdentifier{
network,
},
}, nil
}
// NetworkStatus implements the /network/status endpoint
func (s *NetworkAPI) NetworkStatus(
ctx context.Context, request *types.NetworkRequest,
) (*types.NetworkStatusResponse, *types.Error) {
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
return nil, err
}
// Fetch relevant headers, syncing status, & peers
currentHeader, err := s.hmy.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": fmt.Sprintf("unable to get current header: %v", err.Error()),
})
}
genesisHeader, err := s.hmy.HeaderByNumber(ctx, rpc.BlockNumber(0))
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": fmt.Sprintf("unable to get genesis header: %v", err.Error()),
})
}
peers, rosettaError := getPeersFromNodePeerInfo(s.hmy.GetPeerInfo())
if rosettaError != nil {
return nil, rosettaError
}
targetHeight := int64(s.hmy.NodeAPI.GetMaxPeerHeight())
syncStatus := common.SyncingFinish
if s.hmy.NodeAPI.IsOutOfSync(s.hmy.BeaconChain) {
syncStatus = common.SyncingNewBlock
} else if targetHeight == 0 {
syncStatus = common.SyncingStartup
}
stage := syncStatus.String()
return &types.NetworkStatusResponse{
CurrentBlockIdentifier: &types.BlockIdentifier{
Index: currentHeader.Number().Int64(),
Hash: currentHeader.Hash().String(),
},
CurrentBlockTimestamp: currentHeader.Time().Int64() * 1e3, // Timestamp must be in ms.
GenesisBlockIdentifier: &types.BlockIdentifier{
Index: genesisHeader.Number().Int64(),
Hash: genesisHeader.Hash().String(),
},
Peers: peers,
SyncStatus: &types.SyncStatus{
CurrentIndex: currentHeader.Number().Int64(),
TargetIndex: &targetHeight,
Stage: &stage,
},
}, nil
}
// NetworkOptions implements the /network/options endpoint
func (s *NetworkAPI) NetworkOptions(
ctx context.Context, request *types.NetworkRequest,
) (*types.NetworkOptionsResponse, *types.Error) {
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
return nil, err
}
// Fetch allows based on current network option
var allow *types.Allow
isArchival := nodeconfig.GetDefaultConfig().GetArchival()
if s.hmy.ShardID == shard.BeaconChainShardID {
allow = getBeaconAllow(isArchival)
} else {
allow = getAllow(isArchival)
}
return &types.NetworkOptionsResponse{
Version: &types.Version{
RosettaVersion: common.RosettaVersion,
NodeVersion: nodeconfig.GetVersion(),
},
Allow: allow,
}, nil
}
func getBeaconAllow(isArchival bool) *types.Allow {
return &types.Allow{
OperationStatuses: append(getOperationStatuses(), getBeaconOperationStatuses()...),
OperationTypes: append(common.PlainOperationTypes, common.StakingOperationTypes...),
Errors: append(getErrors(), getBeaconErrors()...),
HistoricalBalanceLookup: isArchival,
}
}
func getAllow(isArchival bool) *types.Allow {
return &types.Allow{
OperationStatuses: getOperationStatuses(),
OperationTypes: common.PlainOperationTypes,
Errors: getErrors(),
HistoricalBalanceLookup: isArchival,
}
}
func getBeaconOperationStatuses() []*types.OperationStatus {
return []*types.OperationStatus{}
}
func getOperationStatuses() []*types.OperationStatus {
return []*types.OperationStatus{
common.SuccessOperationStatus,
common.FailureOperationStatus,
common.ContractFailureOperationStatus,
}
}
func getBeaconErrors() []*types.Error {
return []*types.Error{
&common.StakingTransactionSubmissionError,
}
}
func getErrors() []*types.Error {
return []*types.Error{
&common.CatchAllError,
&common.SanityCheckError,
&common.InvalidNetworkError,
&common.TransactionSubmissionError,
&common.BlockNotFoundError,
&common.TransactionNotFoundError,
&common.ReceiptNotFoundError,
}
}
// getPeersFromNodePeerInfo formats all the unique peers from the NodePeerInfo and
// notes each topic for each peer in the metadata.
func getPeersFromNodePeerInfo(allPeerInfo commonRPC.NodePeerInfo) ([]*types.Peer, *types.Error) {
seenPeerIndex := map[peer.ID]int{}
peers := []*types.Peer{}
for _, peerInfo := range allPeerInfo.P {
for _, pID := range peerInfo.Peers {
i, ok := seenPeerIndex[pID]
if !ok {
newPeer := &types.Peer{
PeerID: pID.String(),
Metadata: map[string]interface{}{
"topics": []string{peerInfo.Topic},
},
}
peers = append(peers, newPeer)
seenPeerIndex[pID] = len(peers) - 1
} else {
topics, ok := peers[i].Metadata["topics"].([]string)
if !ok {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": "could not cast peer metadata to slice of string",
})
}
for _, topic := range topics {
if peerInfo.Topic == topic {
continue
}
}
peers[i].Metadata["topics"] = append(topics, peerInfo.Topic)
}
}
}
return peers, nil
}
func assertValidNetworkIdentifier(netID *types.NetworkIdentifier, shardID uint32) *types.Error {
currNetID, err := common.GetNetwork(shardID)
if err != nil {
return common.NewError(common.SanityCheckError, map[string]interface{}{
"message": fmt.Sprintf("Error while asserting valid network ID: %v", err.Error()),
})
}
// Check for valid network ID and set a message if an error occurs
message := ""
if netID.Blockchain != currNetID.Blockchain {
message = fmt.Sprintf("Invalid blockchain, expected %v", currNetID.Blockchain)
} else if netID.Network != currNetID.Network {
message = fmt.Sprintf("Invalid network, expected %v", currNetID.Network)
} else if netID.SubNetworkIdentifier.Network != currNetID.SubNetworkIdentifier.Network {
message = fmt.Sprintf(
"Invalid subnetwork, expected %v", currNetID.SubNetworkIdentifier.Network,
)
} else {
var metadata, currMetadata common.SubNetworkMetadata
if err := currMetadata.UnmarshalFromInterface(currNetID.SubNetworkIdentifier.Metadata); err != nil {
return common.NewError(common.SanityCheckError, map[string]interface{}{
"message": fmt.Sprintf("Error while asserting valid network ID: %v", err.Error()),
})
}
if err := metadata.UnmarshalFromInterface(netID.SubNetworkIdentifier.Metadata); err != nil {
message = fmt.Sprintf("Subnetwork metadata is of unknown format: %v", err.Error())
}
if metadata.IsBeacon != currMetadata.IsBeacon {
if currMetadata.IsBeacon {
message = "Invalid subnetwork, expected beacon chain subnetwork"
} else {
message = "Invalid subnetwork, expected non-beacon chain subnetwork"
}
}
}
if message != "" {
return common.NewError(common.InvalidNetworkError, map[string]interface{}{
"message": message,
})
}
return nil
}