github.com/coltonfike/e2c@v21.1.0+incompatible/extension/api.go (about) 1 package extension 2 3 import ( 4 "context" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 9 "github.com/ethereum/go-ethereum/accounts/abi/bind" 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/ethereum/go-ethereum/internal/ethapi" 12 "github.com/ethereum/go-ethereum/multitenancy" 13 "github.com/ethereum/go-ethereum/permission/core" 14 "github.com/ethereum/go-ethereum/rpc" 15 ) 16 17 var ( 18 errNotAcceptor = errors.New("account is not acceptor of this extension request") 19 errNotCreator = errors.New("account is not the creator of this extension request") 20 ) 21 22 const extensionCompleted = "DONE" 23 const extensionInProgress = "ACTIVE" 24 25 type PrivateExtensionAPI struct { 26 privacyService *PrivacyService 27 } 28 29 func NewPrivateExtensionAPI(privacyService *PrivacyService) *PrivateExtensionAPI { 30 return &PrivateExtensionAPI{ 31 privacyService: privacyService, 32 } 33 } 34 35 // ActiveExtensionContracts returns the list of all currently outstanding extension contracts 36 func (api *PrivateExtensionAPI) ActiveExtensionContracts() []ExtensionContract { 37 api.privacyService.mu.Lock() 38 defer api.privacyService.mu.Unlock() 39 40 extracted := make([]ExtensionContract, 0, len(api.privacyService.currentContracts)) 41 for _, contract := range api.privacyService.currentContracts { 42 extracted = append(extracted, *contract) 43 } 44 return extracted 45 } 46 47 // checks of the passed contract address is under extension process 48 func (api *PrivateExtensionAPI) checkIfContractUnderExtension(toExtend common.Address) bool { 49 for _, v := range api.ActiveExtensionContracts() { 50 if v.ContractExtended == toExtend { 51 return true 52 } 53 } 54 return false 55 } 56 57 // checks if the voter has already voted on the contract. 58 func (api *PrivateExtensionAPI) checkAlreadyVoted(addressToVoteOn, from common.Address) bool { 59 caller, _ := api.privacyService.managementContractFacade.Caller(addressToVoteOn) 60 opts := bind.CallOpts{Pending: true, From: from} 61 62 voted, _ := caller.CheckIfVoted(&opts) 63 return voted 64 } 65 66 // checks if the voter has already voted on the contract. 67 func (api *PrivateExtensionAPI) checkIfExtensionComplete(addressToVoteOn, from common.Address) (bool, error) { 68 caller, _ := api.privacyService.managementContractFacade.Caller(addressToVoteOn) 69 opts := bind.CallOpts{Pending: true, From: from} 70 71 status, err := caller.CheckIfExtensionFinished(&opts) 72 if err != nil { 73 return true, err 74 } 75 return status, nil 76 } 77 78 // returns the contract being extended for the given management contract 79 func (api *PrivateExtensionAPI) getContractExtended(addressToVoteOn, from common.Address) (common.Address, error) { 80 caller, _ := api.privacyService.managementContractFacade.Caller(addressToVoteOn) 81 opts := bind.CallOpts{Pending: true, From: from} 82 83 return caller.ContractToExtend(&opts) 84 } 85 86 // checks if the contract being extended is a public contract 87 func (api *PrivateExtensionAPI) checkIfPublicContract(toExtend common.Address) bool { 88 // check if the passed contract is public contract 89 publicStateDb, _, _ := api.privacyService.stateFetcher.chainAccessor.State() 90 if publicStateDb != nil && publicStateDb.Exist(toExtend) { 91 return true 92 } 93 return false 94 } 95 96 // checks if the contract being extended is available on the node 97 func (api *PrivateExtensionAPI) checkIfPrivateStateExists(toExtend common.Address) bool { 98 // check if the private contract exists on the node extending the contract 99 _, privateStateDb, _ := api.privacyService.stateFetcher.chainAccessor.State() 100 if privateStateDb != nil { 101 if privateStateDb.GetCode(toExtend) != nil { 102 return true 103 } 104 } 105 return false 106 } 107 108 func (api *PrivateExtensionAPI) doMultiTenantChecks(ctx context.Context, address common.Address, txa ethapi.SendTxArgs) error { 109 apiHelper := api.privacyService.apiBackendHelper 110 if authToken, ok := apiHelper.SupportsMultitenancy(ctx); ok { 111 if len(txa.PrivateFrom) == 0 { 112 return errors.New("You must specify 'privateFrom' when running in a multitenant node") 113 } 114 // check whether the user has access to txa.PrivateFrom and the txa.From eth account 115 attributes := multitenancy.FullAccessContractSecurityAttributes(txa.From, txa.PrivateFrom) 116 chainAccessor := api.privacyService.stateFetcher.chainAccessor 117 currentBlock := chainAccessor.CurrentBlock().Number().Int64() 118 extraDataReader, err := apiHelper.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(currentBlock)) 119 if err != nil { 120 return fmt.Errorf("no account extra data reader at block %v: %w", currentBlock, err) 121 } 122 123 managedParties, err := extraDataReader.GetManagedParties(address) 124 if err != nil { 125 return err 126 } 127 attributes = append(attributes, 128 multitenancy.NewContractSecurityAttributeBuilder().FromEOA(txa.From).Private().Write().Parties(managedParties).Build(), 129 multitenancy.NewContractSecurityAttributeBuilder().FromEOA(txa.From).Private().Read().Parties(managedParties).Build()) 130 131 if authorized, _ := apiHelper.IsAuthorized(ctx, authToken, attributes...); !authorized { 132 return multitenancy.ErrNotAuthorized 133 } 134 } 135 return nil 136 } 137 138 // ApproveContractExtension submits the vote to the specified extension management contract. The vote indicates whether to extend 139 // a given contract to a new participant or not 140 func (api *PrivateExtensionAPI) ApproveExtension(ctx context.Context, addressToVoteOn common.Address, vote bool, txa ethapi.SendTxArgs) (string, error) { 141 err := api.doMultiTenantChecks(ctx, addressToVoteOn, txa) 142 if err != nil { 143 return "", err 144 } 145 // check if the extension has been completed. if yes 146 // no acceptance required 147 status, err := api.checkIfExtensionComplete(addressToVoteOn, txa.From) 148 if err != nil { 149 return "", err 150 } 151 152 if status { 153 return "", errors.New("contract extension process complete. nothing to accept") 154 } 155 156 if !core.CheckIfAdminAccount(txa.From) { 157 return "", errors.New("account cannot accept extension") 158 } 159 160 toExtend, err := api.getContractExtended(addressToVoteOn, txa.From) 161 if err != nil { 162 return "", err 163 } 164 165 // get all participants for the contract being extended 166 participants, err := api.privacyService.GetAllParticipants(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) 167 if err == nil { 168 txa.PrivateFor = append(txa.PrivateFor, participants...) 169 } 170 171 txArgs, err := api.privacyService.GenerateTransactOptions(txa) 172 if err != nil { 173 return "", err 174 } 175 176 voterList, err := api.privacyService.managementContractFacade.GetAllVoters(addressToVoteOn) 177 if err != nil { 178 return "", err 179 } 180 if isVoter := checkAddressInList(txArgs.From, voterList); !isVoter { 181 return "", errNotAcceptor 182 } 183 184 if api.checkAlreadyVoted(addressToVoteOn, txArgs.From) { 185 return "", errors.New("already voted") 186 } 187 uuid, err := generateUuid(addressToVoteOn, txArgs.PrivateFrom, txArgs.PrivateFor, api.privacyService.ptm) 188 if err != nil { 189 return "", err 190 } 191 192 //Find the extension contract in order to interact with it 193 extender, err := api.privacyService.managementContractFacade.Transactor(addressToVoteOn) 194 if err != nil { 195 return "", err 196 } 197 198 //Perform the vote transaction. 199 tx, err := extender.DoVote(txArgs, vote, uuid) 200 if err != nil { 201 return "", err 202 } 203 msg := fmt.Sprintf("0x%x", tx.Hash()) 204 return msg, nil 205 } 206 207 // ExtendContract deploys a new extension management contract to the blockchain to start the process of extending 208 // a contract to a new participant 209 //Create a new extension contract that signifies that we want to add a new participant to an existing contract 210 //This should contain: 211 // - arguments for sending a new transaction (the same as sendTransaction) 212 // - the contract address we want to extend 213 // - the new PTM public key 214 // - the Ethereum addresses of who can vote to extend the contract 215 func (api *PrivateExtensionAPI) ExtendContract(ctx context.Context, toExtend common.Address, newRecipientPtmPublicKey string, recipientAddr common.Address, txa ethapi.SendTxArgs) (string, error) { 216 // check if the contract to be extended is already under extension 217 // if yes throw an error 218 if api.checkIfContractUnderExtension(toExtend) { 219 return "", errors.New("contract extension in progress for the given contract address") 220 } 221 222 // check if a public contract is being extended 223 if api.checkIfPublicContract(toExtend) { 224 return "", errors.New("extending a public contract!!! not allowed") 225 } 226 227 // check if a public contract is being extended 228 if !api.checkIfPrivateStateExists(toExtend) { 229 return "", errors.New("extending a non-existent private contract!!! not allowed") 230 } 231 232 err := api.doMultiTenantChecks(ctx, toExtend, txa) 233 if err != nil { 234 return "", err 235 } 236 237 // check if recipient address is 0x0 238 if recipientAddr == (common.Address{0}) { 239 return "", errors.New("invalid recipient address") 240 } 241 242 // check if contract creator 243 if !api.privacyService.CheckIfContractCreator(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) { 244 return "", errors.New("operation not allowed") 245 } 246 247 // if running in permissioned mode with new permissions model 248 // ensure that the account extending the contract is an admin 249 // account and recipient account is an admin account as well 250 if txa.From == recipientAddr { 251 return "", errors.New("account accepting the extension cannot be the account initiating extension") 252 } 253 if !core.CheckIfAdminAccount(txa.From) { 254 return "", errors.New("account not an org admin account, cannot initiate extension") 255 } 256 if !core.CheckIfAdminAccount(recipientAddr) { 257 return "", errors.New("recipient account address is not an org admin account. cannot accept extension") 258 } 259 260 // check the new key is valid 261 if _, err := base64.StdEncoding.DecodeString(newRecipientPtmPublicKey); err != nil { 262 return "", errors.New("invalid new recipient transaction manager key provided") 263 } 264 265 // check the the intended new recipient will actually receive the extension request 266 switch len(txa.PrivateFor) { 267 case 0: 268 txa.PrivateFor = append(txa.PrivateFor, newRecipientPtmPublicKey) 269 case 1: 270 if txa.PrivateFor[0] != newRecipientPtmPublicKey { 271 return "", errors.New("mismatch between recipient transaction manager key and privateFor argument") 272 } 273 default: 274 return "", errors.New("invalid transaction manager keys given in privateFor argument") 275 } 276 277 // get all participants for the contract being extended 278 participants, err := api.privacyService.GetAllParticipants(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) 279 if err == nil { 280 txa.PrivateFor = append(txa.PrivateFor, participants...) 281 } 282 283 //generate some valid transaction options for sending in the transaction 284 txArgs, err := api.privacyService.GenerateTransactOptions(txa) 285 if err != nil { 286 return "", err 287 } 288 289 //Deploy the contract 290 tx, err := api.privacyService.managementContractFacade.Deploy(txArgs, toExtend, recipientAddr, newRecipientPtmPublicKey) 291 if err != nil { 292 return "", err 293 } 294 295 //Return the transaction hash for later lookup 296 msg := fmt.Sprintf("0x%x", tx.Hash()) 297 return msg, nil 298 } 299 300 // CancelExtension allows the creator to cancel the given extension contract, ensuring 301 // that no more calls for votes or accepting can be made 302 func (api *PrivateExtensionAPI) CancelExtension(ctx context.Context, extensionContract common.Address, txa ethapi.SendTxArgs) (string, error) { 303 err := api.doMultiTenantChecks(ctx, extensionContract, txa) 304 if err != nil { 305 return "", err 306 } 307 308 status, err := api.checkIfExtensionComplete(extensionContract, txa.From) 309 if err != nil { 310 return "", err 311 } 312 if status { 313 return "", errors.New("contract extension process complete. nothing to cancel") 314 } 315 316 toExtend, err := api.getContractExtended(extensionContract, txa.From) 317 if err != nil { 318 return "", err 319 } 320 321 // get all participants for the contract being extended 322 participants, err := api.privacyService.GetAllParticipants(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) 323 if err == nil { 324 txa.PrivateFor = append(txa.PrivateFor, participants...) 325 } 326 327 txArgs, err := api.privacyService.GenerateTransactOptions(txa) 328 if err != nil { 329 return "", err 330 } 331 332 caller, err := api.privacyService.managementContractFacade.Caller(extensionContract) 333 if err != nil { 334 return "", err 335 } 336 creatorAddress, err := caller.Creator(nil) 337 if err != nil { 338 return "", err 339 } 340 if isCreator := checkAddressInList(txArgs.From, []common.Address{creatorAddress}); !isCreator { 341 return "", errNotCreator 342 } 343 344 extender, err := api.privacyService.managementContractFacade.Transactor(extensionContract) 345 if err != nil { 346 return "", err 347 } 348 349 tx, err := extender.Finish(txArgs) 350 if err != nil { 351 return "", err 352 } 353 msg := fmt.Sprintf("0x%x", tx.Hash()) 354 return msg, nil 355 } 356 357 // Returns the extension status from management contract 358 func (api *PrivateExtensionAPI) GetExtensionStatus(ctx context.Context, extensionContract common.Address) (string, error) { 359 apiHelper := api.privacyService.apiBackendHelper 360 if authToken, ok := apiHelper.SupportsMultitenancy(ctx); ok { 361 currentBlock := apiHelper.CurrentBlock().Number().Int64() 362 extraDataReader, err := apiHelper.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(currentBlock)) 363 if err != nil { 364 return "", fmt.Errorf("no account extra data reader at block %v: %w", currentBlock, err) 365 } 366 managedParties, err := extraDataReader.GetManagedParties(extensionContract) 367 if err != nil { 368 return "", err 369 } 370 if authorized, _ := apiHelper.IsAuthorized(ctx, authToken, 371 multitenancy.NewContractSecurityAttributeBuilder().Private().Read().Parties(managedParties).Build()); !authorized { 372 return "", multitenancy.ErrNotAuthorized 373 } 374 } 375 status, err := api.checkIfExtensionComplete(extensionContract, common.Address{}) 376 if err != nil { 377 return "", err 378 } 379 380 if status { 381 return extensionCompleted, nil 382 } 383 384 return extensionInProgress, nil 385 }