Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

Rollup support #37

Open
InoMurko opened this issue Jun 8, 2021 · 5 comments
Open

Rollup support #37

InoMurko opened this issue Jun 8, 2021 · 5 comments

Comments

@InoMurko
Copy link
Contributor

InoMurko commented Jun 8, 2021

API would need to support:

const nonce = await this.signer.getTransactionCount()
    const contractFunction = async (gasPrice): Promise<TransactionReceipt> => {
      this.logger.info('Submitting appendSequencerBatch transaction', {
        gasPrice,
        nonce,
        contractAddr: this.chainContract.address,
      })
      const tx = await this.chainContract.appendSequencerBatch(batchParams, {
        nonce,
        gasPrice,
      })
      this.logger.info('Submitted appendSequencerBatch transaction', {
        txHash: tx.hash,
        from: tx.from,
      })
      this.logger.debug('appendSequencerBatch transaction data', {
        data: tx.data,
      })
      return this.signer.provider.waitForTransaction(
        tx.hash,
        this.numConfirmations
      )
    }
    return this._submitAndLogTx(contractFunction, 'Submitted batch!')
@InoMurko
Copy link
Contributor Author

InoMurko commented Jun 8, 2021

const APPEND_SEQUENCER_BATCH_METHOD_ID = 'appendSequencerBatch()'

const appendSequencerBatch = async (
  OVM_CanonicalTransactionChain: Contract,
  batch: AppendSequencerBatchParams,
  options?: TransactionRequest
): Promise<TransactionResponse> => {
  const methodId = keccak256(
    Buffer.from(APPEND_SEQUENCER_BATCH_METHOD_ID)
  ).slice(2, 10)
  const calldata = encodeAppendSequencerBatch(batch)
  return OVM_CanonicalTransactionChain.signer.sendTransaction({
    to: OVM_CanonicalTransactionChain.address,
    data: '0x' + methodId + calldata,
    ...options,
  })
}

since the data is sent to Ethereum as calldata it is enough if the api support just that. We let the encoding happen in the Sequencer node but remove the signing. So the API would be:

@InoMurko
Copy link
Contributor Author

InoMurko commented Jun 8, 2021

Ah okay, now I understand, I't not only the Sequencer that needs API support for transaction batches but also the Proposer for state roots. And these two actors could be separate PKys!

@InoMurko
Copy link
Contributor Author

InoMurko commented Jun 30, 2021

Perhaps a better approach would be to offer signing capabilities, just like hardware wallets do:
https://github.com/ethers-io/ethers.js/blob/master/packages/hardware-wallets/src.ts/ledger.ts

Also, having Ethers support would mean the integration is easier.

NOT A GOOD IDEA:
because it allows the vault to sign any kind of transaction...

@InoMurko
Copy link
Contributor Author

InoMurko commented Jul 5, 2021

The purpose of this excersise is to pull private keys needed to interact with Rollup contracts.
And to have a good private key managment, because we never see the private key, or able to retrieve one.

I've identified two PKs:

  • SEQUENCER_PRIVATE_KEY == PROPOSER_PRIVATE_KEY
  • relayer L1_WALLET_KEY

The SEQUENCER_PRIVATE_KEY, PROPOSER_PRIVATE_KEY interacts with the following contracts:

OVM_StateCommitmentChain.sol
`appendStateBatch/2`
OVM_CanonicalTransactionChain.sol
`appendSequencerBatch/1`
OVM_CanonicalTransactionChain.sol
`appendQueueBatch/1` (body commented out)

L1_WALLET_KEY interacts with the following contracts:

OVM_L1CrossDomainMessenger.sol
`relayMessage/5` 

OVM_L1CrossDomainMessengerFast.sol
`relayMessage/5`

OVM_L2CrossDomainMessenger.sol
`relayMessage/4`

OVM_L1MultiMessageRelayer.sol
`batchRelayMessages/1`

It is true that anyone can relay messages. So on first look it may seem counter-intuitive to add the
relayer into Vault. But it’s more so that if we fill it up with ETH someone doesn’t steal our private key.

If the Sequencer is accruing fees that represent our revenue, we may need to add a way to transfer ETH out of the account.

The deployer node (in docker compose) uses only the DEPLOYER_PRIVATE_KEY all other PKs are marely used to pull the address:

ovmSequencerAddress: sequencer.address
ovmProposerAddress: proposer.address
ovmRelayerAddress: relayer.address

OVM_StateCommitmentChain.sol
appendStateBatch/2
vault write -output-curl-string immutability-eth-plugin/wallets/sequencer/accounts/$ORIGINAL_AUTHORITY/ovm/appendStateBatch l1=true nonce=1 gas_price=$GAS_PRICE_HIGH batch=$BATCH shouldStartAtElement=$SHOULD_START_AT_ELEMENT contract=$OVM_SCC
where the batch is an array of base64 encoded strings that we decode on the Vault side and transfer into 32 bytes array that fits into the contract API.
value := [32]byte{}
copy(key[:], []byte("hello"))

OVM_CanonicalTransactionChain.sol
appendSequencerBatch/1
vault write -output-curl-string immutability-eth-plugin/wallets/sequencer/accounts/$ORIGINAL_AUTHORITY/ovm/appendSequencerBatch l1=true nonce=1 gas_price=$GAS_PRICE_HIGH batch=$BATCH contract=$OVM_CTC
where the batch uses a custom encoding scheme for efficiency reasons on the contract side (calldata).

In TS this part looks like this:

const calldata = encodeAppendSequencerBatch(batch)
return OVM_CanonicalTransactionChain.signer.sendTransaction({
  to: OVM_CanonicalTransactionChain.address,
  data: '0x' + methodId + calldata,
})

and the encoding part:

export const encodeAppendSequencerBatch = (
  b: AppendSequencerBatchParams
): string => {
  const encodeShouldStartAtElement = encodeHex(b.shouldStartAtElement, 10)
  const encodedTotalElementsToAppend = encodeHex(b.totalElementsToAppend, 6)

  const encodedContextsHeader = encodeHex(b.contexts.length, 6)
  const encodedContexts =
    encodedContextsHeader +
    b.contexts.reduce((acc, cur) => acc + encodeBatchContext(cur), '')

  const encodedTransactionData = b.transactions.reduce((acc, cur) => {
    if (cur.length % 2 !== 0) {
      throw new Error('Unexpected uneven hex string value!')
    }
    const encodedTxDataHeader = remove0x(
      BigNumber.from(remove0x(cur).length / 2).toHexString()
    ).padStart(6, '0')
    return acc + encodedTxDataHeader + remove0x(cur)
  }, '')
  return (
    encodeShouldStartAtElement +
    encodedTotalElementsToAppend +
    encodedContexts +
    encodedTransactionData
  )
}

So in my opinion, I would leave this schema on the sequencer side and let the Vault marely decode base64 and send it through to the contract.
We can do the schema validation on the Vault side (the same that happens on the contract side) so that we prevent arbitrary contract invocation.

OVM_CanonicalTransactionChain.sol
appendQueueBatch/1 (body commented out)
vault write -output-curl-string immutability-eth-plugin/wallets/sequencer/accounts/$ORIGINAL_AUTHORITY/ovm/appendQueueBatch l1=true nonce=1 gas_price=$GAS_PRICE_HIGH num_queued_transactions=$NUM_QUEUED_TRANSACTIONS contract=$OVM_CTC
numQueuedTransactions is an integer

OVM_L1CrossDomainMessenger.sol
relayMessage/5 and OVM_L1CrossDomainMessengerFast.sol relayMessage/5
use the same Solidity API:

function relayMessage(
  address _target,
  address _sender,
  bytes memory _message,
  uint256 _messageNonce,
  L2MessageInclusionProof memory _proof
)

vault write -output-curl-string immutability-eth-plugin/wallets/sequencer/accounts/$ORIGINAL_AUTHORITY/ovm/relayMessage l1=true nonce=1 gas_price=$GAS_PRICE_HIGH target=$TARGET sender=$SENDER message=$MESSAGE message_nonce=$MESSAGE_NONCE proof=$PROOF contract=$OVM_L1CDM
where the proof is just a simple object (example below), message nonce is an integer and everything else a base64 integer string:

const proof1 = {
  stateRoot: ethers.constants.HashZero,
  stateRootBatchHeader: DUMMY_BATCH_HEADERS[0],
  stateRootProof: DUMMY_BATCH_PROOFS[0],
  stateTrieWitness: '0x',
  storageTrieWitness: '0x',
}

OVM_L2CrossDomainMessenger.sol
relayMessage/4
with Solidity API

function relayMessage(
  address _target,
  address _sender,
  bytes memory _message,
  uint256 _messageNonce
)

vault write -output-curl-string immutability-eth-plugin/wallets/sequencer/accounts/$ORIGINAL_AUTHORITY/ovm/relayMessage l1=false nonce=1 gas_price=$GAS_PRICE_HIGH target=$TARGET sender=$SENDER message=$MESSAGE message_nonce=$MESSAGE_NONCE contract=$OVM_L1CDM
same as above

As the Vault could interact both with L1 and L2, the configuration part would need to be expanded so that it supports talking to nodes on l1 and on l2.

vault write immutability-eth-plugin/config rpc_l1_url="$RPC_URL" chain_id="$CHAIN_ID" rpc_l2_url="$RPC_L2_URL" chain_l2_id="$CHAIN_ID_l2"

@InoMurko
Copy link
Contributor Author

InoMurko commented Jul 5, 2021

Thanks @kevsul for the first review.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant