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  }