1051 lines
37 KiB
Python
1051 lines
37 KiB
Python
import datetime
|
|
import re
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from moto.core import BaseBackend, BackendDict, BaseModel
|
|
|
|
from .exceptions import (
|
|
BadRequestException,
|
|
ResourceNotFoundException,
|
|
InvalidRequestException,
|
|
ResourceLimitExceededException,
|
|
ResourceAlreadyExistsException,
|
|
)
|
|
|
|
from .utils import (
|
|
get_network_id,
|
|
get_member_id,
|
|
get_proposal_id,
|
|
get_invitation_id,
|
|
member_name_exist_in_network,
|
|
number_of_members_in_network,
|
|
admin_password_ok,
|
|
get_node_id,
|
|
number_of_nodes_in_member,
|
|
nodes_in_member,
|
|
)
|
|
|
|
FRAMEWORKS = [
|
|
"HYPERLEDGER_FABRIC",
|
|
]
|
|
|
|
FRAMEWORKVERSIONS = [
|
|
"1.2",
|
|
]
|
|
|
|
EDITIONS: Dict[str, Any] = {
|
|
"STARTER": {
|
|
"MaxMembers": 5,
|
|
"MaxNodesPerMember": 2,
|
|
"AllowedNodeInstanceTypes": ["bc.t3.small", "bc.t3.medium"],
|
|
},
|
|
"STANDARD": {
|
|
"MaxMembers": 14,
|
|
"MaxNodesPerMember": 3,
|
|
"AllowedNodeInstanceTypes": ["bc.t3", "bc.m5", "bc.c5"],
|
|
},
|
|
}
|
|
|
|
VOTEVALUES = ["YES", "NO"]
|
|
|
|
|
|
class ManagedBlockchainNetwork(BaseModel):
|
|
def __init__(
|
|
self,
|
|
network_id: str,
|
|
name: str,
|
|
framework: str,
|
|
frameworkversion: str,
|
|
frameworkconfiguration: Dict[str, Any],
|
|
voting_policy: Dict[str, Any],
|
|
member_configuration: Dict[str, Any],
|
|
region: str,
|
|
description: Optional[str] = None,
|
|
):
|
|
self.creationdate = datetime.datetime.utcnow()
|
|
self.id = network_id
|
|
self.name = name
|
|
self.description = description
|
|
self.framework = framework
|
|
self.frameworkversion = frameworkversion
|
|
self.frameworkconfiguration = frameworkconfiguration
|
|
self.voting_policy = voting_policy
|
|
self.member_configuration = member_configuration
|
|
self.region = region
|
|
|
|
@property
|
|
def network_name(self) -> str:
|
|
return self.name
|
|
|
|
@property
|
|
def network_framework(self) -> str:
|
|
return self.framework
|
|
|
|
@property
|
|
def network_framework_version(self) -> str:
|
|
return self.frameworkversion
|
|
|
|
@property
|
|
def network_creationdate(self) -> str:
|
|
return self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z")
|
|
|
|
@property
|
|
def network_description(self) -> Optional[str]:
|
|
return self.description
|
|
|
|
@property
|
|
def network_edition(self) -> str:
|
|
return self.frameworkconfiguration["Fabric"]["Edition"]
|
|
|
|
@property
|
|
def vote_pol_proposal_duration(self) -> float:
|
|
return self.voting_policy["ApprovalThresholdPolicy"]["ProposalDurationInHours"]
|
|
|
|
@property
|
|
def vote_pol_threshold_percentage(self) -> float:
|
|
return self.voting_policy["ApprovalThresholdPolicy"]["ThresholdPercentage"]
|
|
|
|
@property
|
|
def vote_pol_threshold_comparator(self) -> str:
|
|
return self.voting_policy["ApprovalThresholdPolicy"]["ThresholdComparator"]
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
# Format for list_networks
|
|
d = {
|
|
"Id": self.id,
|
|
"Name": self.name,
|
|
"Framework": self.framework,
|
|
"FrameworkVersion": self.frameworkversion,
|
|
"Status": "AVAILABLE",
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
}
|
|
if self.description is not None:
|
|
d["Description"] = self.description
|
|
return d
|
|
|
|
def get_format(self) -> Dict[str, Any]:
|
|
# Format for get_network
|
|
frameworkattributes = {
|
|
"Fabric": {
|
|
"OrderingServiceEndpoint": f"orderer.{self.id.lower()}.managedblockchain.{self.region}.amazonaws.com:30001",
|
|
"Edition": self.frameworkconfiguration["Fabric"]["Edition"],
|
|
}
|
|
}
|
|
|
|
vpcendpointname = (
|
|
f"com.amazonaws.{self.region}.managedblockchain.{self.id.lower()}"
|
|
)
|
|
|
|
d = {
|
|
"Id": self.id,
|
|
"Name": self.name,
|
|
"Framework": self.framework,
|
|
"FrameworkVersion": self.frameworkversion,
|
|
"FrameworkAttributes": frameworkattributes,
|
|
"VpcEndpointServiceName": vpcendpointname,
|
|
"VotingPolicy": self.voting_policy,
|
|
"Status": "AVAILABLE",
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
}
|
|
if self.description is not None:
|
|
d["Description"] = self.description
|
|
return d
|
|
|
|
|
|
class ManagedBlockchainProposal(BaseModel):
|
|
def __init__(
|
|
self,
|
|
proposal_id: str,
|
|
networkid: str,
|
|
memberid: str,
|
|
membername: str,
|
|
numofmembers: int,
|
|
actions: Dict[str, Any],
|
|
network_expiration: float,
|
|
network_threshold: float,
|
|
network_threshold_comp: str,
|
|
description: Optional[str] = None,
|
|
):
|
|
# In general, passing all values instead of creating
|
|
# an apparatus to look them up
|
|
self.id = proposal_id
|
|
self.networkid = networkid
|
|
self.memberid = memberid
|
|
self.membername = membername
|
|
self.numofmembers = numofmembers
|
|
self.actions = actions
|
|
self.network_expiration = network_expiration
|
|
self.network_threshold = network_threshold
|
|
self.network_threshold_comp = network_threshold_comp
|
|
self.description = description
|
|
|
|
self.creationdate = datetime.datetime.utcnow()
|
|
self.expirationdate = self.creationdate + datetime.timedelta(
|
|
hours=network_expiration
|
|
)
|
|
self.yes_vote_count = 0
|
|
self.no_vote_count = 0
|
|
self.outstanding_vote_count = self.numofmembers
|
|
self.status = "IN_PROGRESS"
|
|
self.votes: Dict[str, Dict[str, str]] = {}
|
|
|
|
@property
|
|
def network_id(self) -> str:
|
|
return self.networkid
|
|
|
|
@property
|
|
def proposal_status(self) -> str:
|
|
return self.status
|
|
|
|
@property
|
|
def proposal_votes(self) -> Dict[str, Any]: # type: ignore[misc]
|
|
return self.votes
|
|
|
|
def proposal_actions(self, action_type: str) -> List[Dict[str, Any]]:
|
|
if action_type.lower() == "invitations":
|
|
if "Invitations" in self.actions:
|
|
return self.actions["Invitations"]
|
|
elif action_type.lower() == "removals":
|
|
if "Removals" in self.actions:
|
|
return self.actions["Removals"]
|
|
return []
|
|
|
|
def check_to_expire_proposal(self) -> None:
|
|
if datetime.datetime.utcnow() > self.expirationdate:
|
|
self.status = "EXPIRED"
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
# Format for list_proposals
|
|
return {
|
|
"ProposalId": self.id,
|
|
"ProposedByMemberId": self.memberid,
|
|
"ProposedByMemberName": self.membername,
|
|
"Status": self.status,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"ExpirationDate": self.expirationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
}
|
|
|
|
def get_format(self) -> Dict[str, Any]:
|
|
# Format for get_proposal
|
|
d = {
|
|
"ProposalId": self.id,
|
|
"NetworkId": self.networkid,
|
|
"Actions": self.actions,
|
|
"ProposedByMemberId": self.memberid,
|
|
"ProposedByMemberName": self.membername,
|
|
"Status": self.status,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"ExpirationDate": self.expirationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"YesVoteCount": self.yes_vote_count,
|
|
"NoVoteCount": self.no_vote_count,
|
|
"OutstandingVoteCount": self.outstanding_vote_count,
|
|
}
|
|
if self.description is not None:
|
|
d["Description"] = self.description
|
|
return d
|
|
|
|
def set_vote(self, votermemberid: str, votermembername: str, vote: str) -> None:
|
|
if vote.upper() == "YES":
|
|
self.yes_vote_count += 1
|
|
else:
|
|
self.no_vote_count += 1
|
|
self.outstanding_vote_count -= 1
|
|
|
|
perct_yes = (self.yes_vote_count / self.numofmembers) * 100
|
|
perct_no = (self.no_vote_count / self.numofmembers) * 100
|
|
self.votes[votermemberid] = {
|
|
"MemberId": votermemberid,
|
|
"MemberName": votermembername,
|
|
"Vote": vote.upper(),
|
|
}
|
|
|
|
if self.network_threshold_comp == "GREATER_THAN_OR_EQUAL_TO":
|
|
if perct_yes >= self.network_threshold:
|
|
self.status = "APPROVED"
|
|
elif perct_no >= self.network_threshold:
|
|
self.status = "REJECTED"
|
|
else:
|
|
if perct_yes > self.network_threshold:
|
|
self.status = "APPROVED"
|
|
elif perct_no > self.network_threshold:
|
|
self.status = "REJECTED"
|
|
|
|
# It is a tie - reject
|
|
if (
|
|
self.status == "IN_PROGRESS"
|
|
and self.network_threshold_comp == "GREATER_THAN"
|
|
and self.outstanding_vote_count == 0
|
|
and perct_yes == perct_no
|
|
):
|
|
self.status = "REJECTED"
|
|
|
|
|
|
class ManagedBlockchainInvitation(BaseModel):
|
|
def __init__(
|
|
self,
|
|
invitation_id: str,
|
|
networkid: str,
|
|
networkname: str,
|
|
networkframework: str,
|
|
networkframeworkversion: str,
|
|
networkcreationdate: str,
|
|
region: str,
|
|
networkdescription: Optional[str] = None,
|
|
):
|
|
self.id = invitation_id
|
|
self.networkid = networkid
|
|
self.networkname = networkname
|
|
self.networkdescription = networkdescription
|
|
self.networkframework = networkframework
|
|
self.networkframeworkversion = networkframeworkversion
|
|
self.networkstatus = "AVAILABLE"
|
|
self.networkcreationdate = networkcreationdate
|
|
self.status = "PENDING"
|
|
self.region = region
|
|
|
|
self.creationdate = datetime.datetime.utcnow()
|
|
self.expirationdate = self.creationdate + datetime.timedelta(days=7)
|
|
|
|
@property
|
|
def invitation_status(self) -> str:
|
|
return self.status
|
|
|
|
@property
|
|
def invitation_networkid(self) -> str:
|
|
return self.networkid
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
d: Dict[str, Any] = {
|
|
"InvitationId": self.id,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"ExpirationDate": self.expirationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"Status": self.status,
|
|
"NetworkSummary": {
|
|
"Id": self.networkid,
|
|
"Name": self.networkname,
|
|
"Framework": self.networkframework,
|
|
"FrameworkVersion": self.networkframeworkversion,
|
|
"Status": self.networkstatus,
|
|
"CreationDate": self.networkcreationdate,
|
|
},
|
|
}
|
|
if self.networkdescription is not None:
|
|
d["NetworkSummary"]["Description"] = self.networkdescription
|
|
return d
|
|
|
|
def accept_invitation(self) -> None:
|
|
self.status = "ACCEPTED"
|
|
|
|
def reject_invitation(self) -> None:
|
|
self.status = "REJECTED"
|
|
|
|
def set_network_status(self, network_status: str) -> None:
|
|
self.networkstatus = network_status
|
|
|
|
|
|
class ManagedBlockchainMember(BaseModel):
|
|
def __init__(
|
|
self,
|
|
member_id: str,
|
|
networkid: str,
|
|
member_configuration: Dict[str, Any],
|
|
region: str,
|
|
):
|
|
self.creationdate = datetime.datetime.utcnow()
|
|
self.id = member_id
|
|
self.networkid = networkid
|
|
self.member_configuration = member_configuration
|
|
self.status = "AVAILABLE"
|
|
self.region = region
|
|
self.description = None
|
|
|
|
@property
|
|
def network_id(self) -> str:
|
|
return self.networkid
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return self.member_configuration["Name"]
|
|
|
|
@property
|
|
def member_status(self) -> str:
|
|
return self.status
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
# Format for list_members
|
|
d = {
|
|
"Id": self.id,
|
|
"Name": self.member_configuration["Name"],
|
|
"Status": self.status,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"IsOwned": True,
|
|
}
|
|
if "Description" in self.member_configuration:
|
|
self.description = self.member_configuration["Description"]
|
|
return d
|
|
|
|
def get_format(self) -> Dict[str, Any]:
|
|
# Format for get_member
|
|
frameworkattributes = {
|
|
"Fabric": {
|
|
"AdminUsername": self.member_configuration["FrameworkConfiguration"][
|
|
"Fabric"
|
|
]["AdminUsername"],
|
|
"CaEndpoint": f"ca.{self.id.lower()}.{self.networkid.lower()}.managedblockchain.{self.region}.amazonaws.com:30002",
|
|
}
|
|
}
|
|
|
|
d = {
|
|
"NetworkId": self.networkid,
|
|
"Id": self.id,
|
|
"Name": self.name,
|
|
"FrameworkAttributes": frameworkattributes,
|
|
"LogPublishingConfiguration": self.member_configuration[
|
|
"LogPublishingConfiguration"
|
|
],
|
|
"Status": self.status,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
}
|
|
if "Description" in self.member_configuration:
|
|
d["Description"] = self.description
|
|
return d
|
|
|
|
def delete(self) -> None:
|
|
self.status = "DELETED"
|
|
|
|
def update(self, logpublishingconfiguration: Dict[str, Any]) -> None:
|
|
self.member_configuration[
|
|
"LogPublishingConfiguration"
|
|
] = logpublishingconfiguration
|
|
|
|
|
|
class ManagedBlockchainNode(BaseModel):
|
|
def __init__(
|
|
self,
|
|
node_id: str,
|
|
networkid: str,
|
|
memberid: str,
|
|
availabilityzone: str,
|
|
instancetype: str,
|
|
logpublishingconfiguration: Dict[str, Any],
|
|
region: str,
|
|
):
|
|
self.creationdate = datetime.datetime.utcnow()
|
|
self.id = node_id
|
|
self.instancetype = instancetype
|
|
self.networkid = networkid
|
|
self.memberid = memberid
|
|
self.logpublishingconfiguration = logpublishingconfiguration
|
|
self.region = region
|
|
self.status = "AVAILABLE"
|
|
self.availabilityzone = availabilityzone
|
|
|
|
@property
|
|
def member_id(self) -> str:
|
|
return self.memberid
|
|
|
|
@property
|
|
def node_status(self) -> str:
|
|
return self.status
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
# Format for list_nodes
|
|
return {
|
|
"Id": self.id,
|
|
"Status": self.status,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
"AvailabilityZone": self.availabilityzone,
|
|
"InstanceType": self.instancetype,
|
|
}
|
|
|
|
def get_format(self) -> Dict[str, Any]:
|
|
# Format for get_node
|
|
frameworkattributes = {
|
|
"Fabric": {
|
|
"PeerEndpoint": f"{self.id.lower()}.{self.networkid.lower()}.{self.memberid.lower()}.managedblockchain.{self.region}.amazonaws.com:30003",
|
|
"PeerEventEndpoint": f"{self.id.lower()}.{self.networkid.lower()}.{self.memberid.lower()}.managedblockchain.{self.region}.amazonaws.com:30004",
|
|
}
|
|
}
|
|
|
|
return {
|
|
"NetworkId": self.networkid,
|
|
"MemberId": self.memberid,
|
|
"Id": self.id,
|
|
"InstanceType": self.instancetype,
|
|
"AvailabilityZone": self.availabilityzone,
|
|
"FrameworkAttributes": frameworkattributes,
|
|
"LogPublishingConfiguration": self.logpublishingconfiguration,
|
|
"Status": self.status,
|
|
"CreationDate": self.creationdate.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
|
|
}
|
|
|
|
def delete(self) -> None:
|
|
self.status = "DELETED"
|
|
|
|
def update(self, logpublishingconfiguration: Dict[str, Any]) -> None:
|
|
self.logpublishingconfiguration = logpublishingconfiguration
|
|
|
|
|
|
class ManagedBlockchainBackend(BaseBackend):
|
|
def __init__(self, region_name: str, account_id: str):
|
|
super().__init__(region_name, account_id)
|
|
self.networks: Dict[str, ManagedBlockchainNetwork] = {}
|
|
self.members: Dict[str, ManagedBlockchainMember] = {}
|
|
self.proposals: Dict[str, ManagedBlockchainProposal] = {}
|
|
self.invitations: Dict[str, ManagedBlockchainInvitation] = {}
|
|
self.nodes: Dict[str, ManagedBlockchainNode] = {}
|
|
|
|
def create_network(
|
|
self,
|
|
name: str,
|
|
framework: str,
|
|
frameworkversion: str,
|
|
frameworkconfiguration: Dict[str, Any],
|
|
voting_policy: Dict[str, Any],
|
|
member_configuration: Dict[str, Any],
|
|
description: Optional[str] = None,
|
|
) -> Dict[str, str]:
|
|
# Check framework
|
|
if framework not in FRAMEWORKS:
|
|
raise BadRequestException("CreateNetwork", "Invalid request body")
|
|
|
|
# Check framework version
|
|
if frameworkversion not in FRAMEWORKVERSIONS:
|
|
raise BadRequestException(
|
|
"CreateNetwork",
|
|
f"Invalid version {frameworkversion} requested for framework HYPERLEDGER_FABRIC",
|
|
)
|
|
|
|
# Check edition
|
|
if frameworkconfiguration["Fabric"]["Edition"] not in EDITIONS:
|
|
raise BadRequestException("CreateNetwork", "Invalid request body")
|
|
|
|
# Generate network ID
|
|
network_id = get_network_id()
|
|
|
|
# Generate memberid ID and initial member
|
|
member_id = get_member_id()
|
|
self.members[member_id] = ManagedBlockchainMember(
|
|
member_id=member_id,
|
|
networkid=network_id,
|
|
member_configuration=member_configuration,
|
|
region=self.region_name,
|
|
)
|
|
|
|
self.networks[network_id] = ManagedBlockchainNetwork(
|
|
network_id=network_id,
|
|
name=name,
|
|
framework=framework,
|
|
frameworkversion=frameworkversion,
|
|
frameworkconfiguration=frameworkconfiguration,
|
|
voting_policy=voting_policy,
|
|
member_configuration=member_configuration,
|
|
region=self.region_name,
|
|
description=description,
|
|
)
|
|
|
|
# Return the network and member ID
|
|
return {"NetworkId": network_id, "MemberId": member_id}
|
|
|
|
def list_networks(self) -> List[ManagedBlockchainNetwork]:
|
|
return list(self.networks.values())
|
|
|
|
def get_network(self, network_id: str) -> ManagedBlockchainNetwork:
|
|
if network_id not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"GetNetwork", f"Network {network_id} not found."
|
|
)
|
|
return self.networks[network_id]
|
|
|
|
def create_proposal(
|
|
self,
|
|
networkid: str,
|
|
memberid: str,
|
|
actions: Dict[str, Any],
|
|
description: Optional[str] = None,
|
|
) -> Dict[str, str]:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"CreateProposal", f"Network {networkid} not found."
|
|
)
|
|
|
|
# Check if member exists
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"CreateProposal", f"Member {memberid} not found."
|
|
)
|
|
|
|
# CLI docs say that Invitations and Removals cannot both be passed - but it does
|
|
# not throw an error and can be performed
|
|
if "Invitations" in actions:
|
|
for propinvitation in actions["Invitations"]:
|
|
if re.match("[0-9]{12}", propinvitation["Principal"]) is None:
|
|
raise InvalidRequestException(
|
|
"CreateProposal",
|
|
"Account ID format specified in proposal is not valid.",
|
|
)
|
|
|
|
if "Removals" in actions:
|
|
for propmember in actions["Removals"]:
|
|
if propmember["MemberId"] not in self.members:
|
|
raise InvalidRequestException(
|
|
"CreateProposal",
|
|
"Member ID format specified in proposal is not valid.",
|
|
)
|
|
|
|
# Generate proposal ID
|
|
proposal_id = get_proposal_id()
|
|
|
|
self.proposals[proposal_id] = ManagedBlockchainProposal(
|
|
proposal_id=proposal_id,
|
|
networkid=networkid,
|
|
memberid=memberid,
|
|
membername=self.members[memberid].name,
|
|
numofmembers=number_of_members_in_network(self.members, networkid),
|
|
actions=actions,
|
|
network_expiration=self.networks[networkid].vote_pol_proposal_duration,
|
|
network_threshold=self.networks[networkid].vote_pol_threshold_percentage,
|
|
network_threshold_comp=self.networks[
|
|
networkid
|
|
].vote_pol_threshold_comparator,
|
|
description=description,
|
|
)
|
|
|
|
# Return the proposal ID
|
|
return {"ProposalId": proposal_id}
|
|
|
|
def list_proposals(self, networkid: str) -> List[ManagedBlockchainProposal]:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"ListProposals", f"Network {networkid} not found."
|
|
)
|
|
|
|
proposalsfornetwork = []
|
|
for proposal_id in self.proposals:
|
|
if self.proposals[proposal_id].network_id == networkid:
|
|
# See if any are expired
|
|
self.proposals[proposal_id].check_to_expire_proposal()
|
|
proposalsfornetwork.append(self.proposals[proposal_id])
|
|
return proposalsfornetwork
|
|
|
|
def get_proposal(
|
|
self, networkid: str, proposalid: str
|
|
) -> ManagedBlockchainProposal:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"GetProposal", f"Network {networkid} not found."
|
|
)
|
|
|
|
if proposalid not in self.proposals:
|
|
raise ResourceNotFoundException(
|
|
"GetProposal", f"Proposal {proposalid} not found."
|
|
)
|
|
|
|
# See if it needs to be set to expipred
|
|
self.proposals[proposalid].check_to_expire_proposal()
|
|
return self.proposals[proposalid]
|
|
|
|
def vote_on_proposal(
|
|
self, networkid: str, proposalid: str, votermemberid: str, vote: str
|
|
) -> None:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"VoteOnProposal", f"Network {networkid} not found."
|
|
)
|
|
|
|
if proposalid not in self.proposals:
|
|
raise ResourceNotFoundException(
|
|
"VoteOnProposal", f"Proposal {proposalid} not found."
|
|
)
|
|
|
|
if votermemberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"VoteOnProposal", f"Member {votermemberid} not found."
|
|
)
|
|
|
|
if vote.upper() not in VOTEVALUES:
|
|
raise BadRequestException("VoteOnProposal", "Invalid request body")
|
|
|
|
# See if it needs to be set to expipred
|
|
self.proposals[proposalid].check_to_expire_proposal()
|
|
|
|
# Exception if EXPIRED
|
|
if self.proposals[proposalid].proposal_status == "EXPIRED":
|
|
raise InvalidRequestException(
|
|
"VoteOnProposal",
|
|
f"Proposal {proposalid} is expired and you cannot vote on it.",
|
|
)
|
|
|
|
# Check if IN_PROGRESS
|
|
if self.proposals[proposalid].proposal_status != "IN_PROGRESS":
|
|
raise InvalidRequestException(
|
|
"VoteOnProposal",
|
|
f"Proposal {proposalid} has status {self.proposals[proposalid].proposal_status} and you cannot vote on it.",
|
|
)
|
|
|
|
# Check to see if this member already voted
|
|
if votermemberid in self.proposals[proposalid].proposal_votes:
|
|
raise ResourceAlreadyExistsException(
|
|
"VoteOnProposal",
|
|
f"Member {votermemberid} has already voted on proposal {proposalid}.",
|
|
)
|
|
|
|
# Cast vote
|
|
self.proposals[proposalid].set_vote(
|
|
votermemberid, self.members[votermemberid].name, vote.upper()
|
|
)
|
|
|
|
if self.proposals[proposalid].proposal_status == "APPROVED":
|
|
# Generate invitations
|
|
for _ in self.proposals[proposalid].proposal_actions("Invitations"):
|
|
invitation_id = get_invitation_id()
|
|
self.invitations[invitation_id] = ManagedBlockchainInvitation(
|
|
invitation_id=invitation_id,
|
|
networkid=networkid,
|
|
networkname=self.networks[networkid].network_name,
|
|
networkframework=self.networks[networkid].network_framework,
|
|
networkframeworkversion=self.networks[
|
|
networkid
|
|
].network_framework_version,
|
|
networkcreationdate=self.networks[networkid].network_creationdate,
|
|
region=self.region_name,
|
|
networkdescription=self.networks[networkid].network_description,
|
|
)
|
|
|
|
# Delete members
|
|
for propmember in self.proposals[proposalid].proposal_actions("Removals"):
|
|
self.delete_member(networkid, propmember["MemberId"])
|
|
|
|
def list_proposal_votes(self, networkid: str, proposalid: str) -> List[str]:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"ListProposalVotes", f"Network {networkid} not found."
|
|
)
|
|
|
|
if proposalid not in self.proposals:
|
|
raise ResourceNotFoundException(
|
|
"ListProposalVotes", f"Proposal {proposalid} not found."
|
|
)
|
|
|
|
# Output the vote summaries
|
|
proposalvotesfornetwork = []
|
|
for proposal in self.proposals.values():
|
|
if proposal.network_id == networkid:
|
|
for proposal_vote in proposal.proposal_votes.values():
|
|
proposalvotesfornetwork.append(proposal_vote)
|
|
return proposalvotesfornetwork
|
|
|
|
def list_invitations(self) -> List[ManagedBlockchainInvitation]:
|
|
return list(self.invitations.values())
|
|
|
|
def reject_invitation(self, invitationid: str) -> None:
|
|
if invitationid not in self.invitations:
|
|
raise ResourceNotFoundException(
|
|
"RejectInvitation", f"InvitationId {invitationid} not found."
|
|
)
|
|
self.invitations[invitationid].reject_invitation()
|
|
|
|
def create_member(
|
|
self, invitationid: str, networkid: str, member_configuration: Dict[str, Any]
|
|
) -> Dict[str, str]:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"CreateMember", f"Network {networkid} not found."
|
|
)
|
|
|
|
if invitationid not in self.invitations:
|
|
raise InvalidRequestException(
|
|
"CreateMember", f"Invitation {invitationid} not valid"
|
|
)
|
|
|
|
if self.invitations[invitationid].invitation_status != "PENDING":
|
|
raise InvalidRequestException(
|
|
"CreateMember", f"Invitation {invitationid} not valid"
|
|
)
|
|
|
|
if member_name_exist_in_network(
|
|
self.members, networkid, member_configuration["Name"]
|
|
):
|
|
raise InvalidRequestException(
|
|
"CreateMember",
|
|
f"Member name {member_configuration['Name']} already exists in network {networkid}.",
|
|
)
|
|
|
|
networkedition = self.networks[networkid].network_edition
|
|
if (
|
|
number_of_members_in_network(self.members, networkid)
|
|
>= EDITIONS[networkedition]["MaxMembers"]
|
|
):
|
|
raise ResourceLimitExceededException(
|
|
"CreateMember",
|
|
f"You cannot create a member in network {networkid}.{EDITIONS[networkedition]['MaxMembers']} is the maximum number of members allowed in a {networkedition} Edition network.",
|
|
)
|
|
|
|
memberadminpassword = member_configuration["FrameworkConfiguration"]["Fabric"][
|
|
"AdminPassword"
|
|
]
|
|
if admin_password_ok(memberadminpassword) is False:
|
|
raise BadRequestException("CreateMember", "Invalid request body")
|
|
|
|
member_id = get_member_id()
|
|
self.members[member_id] = ManagedBlockchainMember(
|
|
member_id=member_id,
|
|
networkid=networkid,
|
|
member_configuration=member_configuration,
|
|
region=self.region_name,
|
|
)
|
|
|
|
# Accept the invitaiton
|
|
self.invitations[invitationid].accept_invitation()
|
|
|
|
# Return the member ID
|
|
return {"MemberId": member_id}
|
|
|
|
def list_members(self, networkid: str) -> List[ManagedBlockchainMember]:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"ListMembers", f"Network {networkid} not found."
|
|
)
|
|
|
|
membersfornetwork = []
|
|
for member in self.members.values():
|
|
if member.network_id == networkid:
|
|
membersfornetwork.append(member)
|
|
return membersfornetwork
|
|
|
|
def get_member(self, networkid: str, memberid: str) -> ManagedBlockchainMember:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"GetMember", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"GetMember", f"Member {memberid} not found."
|
|
)
|
|
|
|
# Cannot get a member than has been deleted (it does show up in the list)
|
|
if self.members[memberid].member_status == "DELETED":
|
|
raise ResourceNotFoundException(
|
|
"GetMember", f"Member {memberid} not found."
|
|
)
|
|
|
|
return self.members[memberid]
|
|
|
|
def delete_member(self, networkid: str, memberid: str) -> None:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"DeleteMember", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"DeleteMember", f"Member {memberid} not found."
|
|
)
|
|
|
|
self.members[memberid].delete()
|
|
|
|
# Is this the last member in the network? (all set to DELETED)
|
|
if number_of_members_in_network(
|
|
self.members, networkid, member_status="DELETED"
|
|
) == len(self.members):
|
|
# Set network status to DELETED for all invitations
|
|
for invitation in self.invitations.values():
|
|
if invitation.invitation_networkid == networkid:
|
|
invitation.set_network_status("DELETED")
|
|
|
|
# Remove network
|
|
del self.networks[networkid]
|
|
|
|
# Remove any nodes associated
|
|
for nodeid in nodes_in_member(self.nodes, memberid):
|
|
del self.nodes[nodeid]
|
|
|
|
def update_member(
|
|
self, networkid: str, memberid: str, logpublishingconfiguration: Dict[str, Any]
|
|
) -> None:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"UpdateMember", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"UpdateMember", f"Member {memberid} not found."
|
|
)
|
|
|
|
self.members[memberid].update(logpublishingconfiguration)
|
|
|
|
def create_node(
|
|
self,
|
|
networkid: str,
|
|
memberid: str,
|
|
availabilityzone: str,
|
|
instancetype: str,
|
|
logpublishingconfiguration: Dict[str, Any],
|
|
) -> Dict[str, str]:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"CreateNode", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"CreateNode", f"Member {memberid} not found."
|
|
)
|
|
|
|
networkedition = self.networks[networkid].network_edition
|
|
if (
|
|
number_of_nodes_in_member(self.nodes, memberid)
|
|
>= EDITIONS[networkedition]["MaxNodesPerMember"]
|
|
):
|
|
raise ResourceLimitExceededException(
|
|
"CreateNode",
|
|
f"Maximum number of nodes exceeded in member {memberid}. The maximum number of nodes you can have in a member in a {networkedition} Edition network is {EDITIONS[networkedition]['MaxNodesPerMember']}",
|
|
)
|
|
|
|
# See if the instance family is correct
|
|
correctinstancefamily = False
|
|
for chkinsttypepre in EDITIONS["STANDARD"]["AllowedNodeInstanceTypes"]:
|
|
chkinsttypepreregex = chkinsttypepre + ".*"
|
|
if re.match(chkinsttypepreregex, instancetype, re.IGNORECASE):
|
|
correctinstancefamily = True
|
|
break
|
|
|
|
if correctinstancefamily is False:
|
|
raise InvalidRequestException(
|
|
"CreateNode",
|
|
f"Requested instance {instancetype} isn't supported.",
|
|
)
|
|
|
|
# Check for specific types for starter
|
|
if networkedition == "STARTER":
|
|
if instancetype not in EDITIONS["STARTER"]["AllowedNodeInstanceTypes"]:
|
|
raise InvalidRequestException(
|
|
"CreateNode",
|
|
f"Instance type {instancetype} is not supported with STARTER Edition networks.",
|
|
)
|
|
|
|
# Simple availability zone check
|
|
chkregionpreregex = self.region_name + "[a-z]"
|
|
if re.match(chkregionpreregex, availabilityzone, re.IGNORECASE) is None:
|
|
raise InvalidRequestException(
|
|
"CreateNode", "Availability Zone is not valid"
|
|
)
|
|
|
|
node_id = get_node_id()
|
|
self.nodes[node_id] = ManagedBlockchainNode(
|
|
node_id=node_id,
|
|
networkid=networkid,
|
|
memberid=memberid,
|
|
availabilityzone=availabilityzone,
|
|
instancetype=instancetype,
|
|
logpublishingconfiguration=logpublishingconfiguration,
|
|
region=self.region_name,
|
|
)
|
|
|
|
# Return the node ID
|
|
return {"NodeId": node_id}
|
|
|
|
def list_nodes(
|
|
self, networkid: str, memberid: str, status: Optional[str] = None
|
|
) -> List[ManagedBlockchainNode]:
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"ListNodes", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"ListNodes", f"Member {memberid} not found."
|
|
)
|
|
|
|
# If member is deleted, cannot list nodes
|
|
if self.members[memberid].member_status == "DELETED":
|
|
raise ResourceNotFoundException(
|
|
"ListNodes", f"Member {memberid} not found."
|
|
)
|
|
|
|
nodesformember = []
|
|
for node in self.nodes.values():
|
|
if node.member_id == memberid and (
|
|
status is None or node.node_status == status
|
|
):
|
|
nodesformember.append(node)
|
|
return nodesformember
|
|
|
|
def get_node(
|
|
self, networkid: str, memberid: str, nodeid: str
|
|
) -> ManagedBlockchainNode:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"GetNode", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException("GetNode", f"Member {memberid} not found.")
|
|
|
|
if nodeid not in self.nodes:
|
|
raise ResourceNotFoundException("GetNode", f"Node {nodeid} not found.")
|
|
|
|
# Cannot get a node than has been deleted (it does show up in the list)
|
|
if self.nodes[nodeid].node_status == "DELETED":
|
|
raise ResourceNotFoundException("GetNode", f"Node {nodeid} not found.")
|
|
|
|
return self.nodes[nodeid]
|
|
|
|
def delete_node(self, networkid: str, memberid: str, nodeid: str) -> None:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"DeleteNode", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"DeleteNode", f"Member {memberid} not found."
|
|
)
|
|
|
|
if nodeid not in self.nodes:
|
|
raise ResourceNotFoundException("DeleteNode", f"Node {nodeid} not found.")
|
|
|
|
self.nodes[nodeid].delete()
|
|
|
|
def update_node(
|
|
self,
|
|
networkid: str,
|
|
memberid: str,
|
|
nodeid: str,
|
|
logpublishingconfiguration: Dict[str, Any],
|
|
) -> None:
|
|
# Check if network exists
|
|
if networkid not in self.networks:
|
|
raise ResourceNotFoundException(
|
|
"UpdateNode", f"Network {networkid} not found."
|
|
)
|
|
|
|
if memberid not in self.members:
|
|
raise ResourceNotFoundException(
|
|
"UpdateNode", f"Member {memberid} not found."
|
|
)
|
|
|
|
if nodeid not in self.nodes:
|
|
raise ResourceNotFoundException("UpdateNode", f"Node {nodeid} not found.")
|
|
|
|
self.nodes[nodeid].update(logpublishingconfiguration)
|
|
|
|
|
|
managedblockchain_backends = BackendDict(ManagedBlockchainBackend, "managedblockchain")
|