truffle migrate 部署合约流程

源码分析truffle migrate做了什么

带点感觉,带点逻辑推测,并没有跟踪truffle migrate的执行路径。

///truffle/packages/core/lib/commands/deploy.js

const migrate = require("./migrate");

const command = {
  command: "deploy",
  description: "(alias for migrate)",
  builder: migrate.builder,
  help: {
    usage:
      "truffle deploy [--reset] [-f <number>] [--compile-all] [--verbose-rpc]",
    options: migrate.help.options,
    allowedGlobalOptions: ["network", "config"]
  },
  run: migrate.run
};

module.exports = command;

调用deploy()这个函数:

//truffle/packages/contract/lib/execute.js

  /**
   * Deploys an instance
   * @param  {Object} constructorABI  Constructor ABI segment w/ inputs & outputs keys
   * @return {PromiEvent}             Resolves a TruffleContract instance
   */
  deploy: function (constructorABI) {
    const constructor = this;
    const web3 = constructor.web3;

    return function () {
      let deferred;
      const promiEvent = new PromiEvent(false, constructor.debugger, true);

	  //先调用prepareCall()函数处理参数、组装参数
      execute
        .prepareCall(constructor, constructorABI, arguments)
        .then(async ({ args, params, network }) => {
          const { blockLimit } = network;

          utils.checkLibraries.apply(constructor);

          // Promievent and flag that allows instance to resolve (rather than just receipt)
          const context = {
            contract: constructor,
            promiEvent,
            onlyEmitReceipt: true
          };

          const options = {
            data: constructor.binary,
            arguments: args
          };

          const contract = new web3.eth.Contract(constructor.abi);
          params.data = contract.deploy(options).encodeABI();

          params.gas = await execute.getGasEstimate.call(
            constructor,
            params,
            blockLimit
          );

          context.params = params;

          promiEvent.eventEmitter.emit("execute:deploy:method", {
            args,
            abi: constructorABI,
            contract: constructor
          });
		  
		  //调用sendTransaction()函数发送交易
          deferred = execute.sendTransaction(web3, params, promiEvent, context); //the crazy things we do for stacktracing...

          try {
            const receipt = await deferred;
            if (receipt.status !== undefined && !receipt.status) {
              const reason = await Reason.get(params, web3);

              const error = new StatusError(
                params,
                context.transactionHash,
                receipt,
                reason
              );

              return context.promiEvent.reject(error);
            }

            const web3Instance = new web3.eth.Contract(
              constructor.abi,
              receipt.contractAddress
            );
            web3Instance.transactionHash = context.transactionHash;

            context.promiEvent.resolve(new constructor(web3Instance));
          } catch (web3Error) {
            // Manage web3's 50 blocks' timeout error.
            // Web3's own subscriptions go dead here.
            await override.start.call(constructor, context, web3Error);
          }
        })
        .catch(promiEvent.reject);

      return promiEvent.eventEmitter;
    };
  },

prepareCall()函数:

  /**
   * Prepares simple wrapped calls by checking network and organizing the method inputs into
   * objects web3 can consume.
   * @param  {Object} constructor   TruffleContract constructor
   * @param  {Object} methodABI     Function ABI segment w/ inputs & outputs keys.
   * @param  {Array}  _arguments    Arguments passed to method invocation
   * @return {Promise}              Resolves object w/ tx params disambiguated from arguments
   */
  prepareCall: async function (constructor, methodABI, _arguments) {
    let args = Array.prototype.slice.call(_arguments);
    let params = utils.getTxParams.call(constructor, methodABI, args);

    args = utils.convertToEthersBN(args);

    if (constructor.ens && constructor.ens.enabled) {
      const { web3 } = constructor;
      const processedValues = await utils.ens.convertENSNames({
        networkId: constructor.network_id,
        ensSettings: constructor.ens,
        inputArgs: args,
        inputParams: params,
        methodABI,
        web3
      });
      args = processedValues.args;
      params = processedValues.params;
    }

    const network = await constructor.detectNetwork();
    return { args, params, network };
  },

sendTransaction()函数

  //our own custom sendTransaction function, made to mimic web3's,
  //while also being able to do things, like, say, store the transaction
  //hash even in case of failure.  it's not as powerful in some ways,
  //as it just returns an ordinary Promise rather than web3's PromiEvent,
  //but it's more suited to our purposes (we're not using that PromiEvent
  //functionality here anyway)
  //input works the same as input to web3.sendTransaction
  //(well, OK, it's lacking some things there too, but again, good enough
  //for our purposes)
  sendTransaction: async function (web3, params, promiEvent, context) {
    //if we don't need the debugger, let's not risk any errors on our part,
    //and just have web3 do everything
    if (!promiEvent || !promiEvent.debug) {
      const deferred = web3.eth.sendTransaction(params);
      handlers.setup(deferred, context);
      return deferred;
    }
    //otherwise, do things manually!
    //(and skip the PromiEvent stuff :-/ )
    return sendTransactionManual(web3, params, promiEvent);
  }

truffle migrate部署合约流程

根据truffle migrate 输出的log来分析:

/MetaCoin$ truffle migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'development'
> Network id:      1500
> Block gas limit: 15000000 (0xe4e1c0)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------

Error:  *** Deployment Failed ***

"Migrations" -- Returned error: no signer available.

    at /usr/local/lib/node_modules/truffle/build/webpack:/packages/deployer/src/deployment.js:365:1
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at Migration._deploy (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/Migration.js:70:1)
    at Migration._load (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/Migration.js:56:1)
    at Migration.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/Migration.js:217:1)
    at Object.runMigrations (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:150:1)
    at Object.runFrom (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:110:1)
    at Object.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:87:1)
    at runMigrations (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/commands/migrate.js:258:1)
    at Object.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/commands/migrate.js:223:1)
    at Command.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/command.js:172:1)

查看deployment.js的365行的代码,是在executeDeployment()函数内:

//IdeaProjects/truffle/packages/deployer/src/deployment.js


  /**
   *
   * @param  {Object} contract  Contract abstraction
   * @param  {Array}  args      Constructor arguments
   * @return {Promise}          Resolves an instance
   */
  executeDeployment(contract, args) {
    const self = this;

    return async function() {
      await self._preFlightCheck(contract);

      let instance;
      let eventArgs;
      let shouldDeploy = true;
      let state = {
        contractName: contract.contractName
      };

      const isDeployed = contract.isDeployed();
      const newArgs = await Promise.all(args);
      const currentBlock = await contract.interfaceAdapter.getBlock("latest");

      // Last arg can be an object that tells us not to overwrite.
      if (newArgs.length > 0) {
        shouldDeploy = self._canOverwrite(newArgs, isDeployed);
      }

      // Case: deploy:
      if (shouldDeploy) {
        /*
          Set timeout override. If this value is zero,
          @truffle/contract will defer to web3's defaults:
          - 50 blocks (websockets) OR 50 * 15sec (http)
        */
        contract.timeoutBlocks = self.timeoutBlocks;

        eventArgs = {
          state: state,
          contract: contract,
          deployed: isDeployed,
          blockLimit: currentBlock.gasLimit,
          gas: self._extractFromArgs(newArgs, "gas") || contract.defaults().gas,
          gasPrice:
            self._extractFromArgs(newArgs, "gasPrice") ||
            contract.defaults().gasPrice,
          from:
            self._extractFromArgs(newArgs, "from") || contract.defaults().from
        };

        // Get an estimate for previews / detect constructor revert
        // NB: web3 does not strip the revert msg here like it does for `deploy`
        try {
          eventArgs.estimate = await contract.new.estimateGas.apply(
            contract,
            newArgs
          );
        } catch (err) {
          eventArgs.estimateError = err;
        }

        // Emit `preDeploy` & send transaction
        await self.emitter.emit("preDeploy", eventArgs);
        const promiEvent = contract.new.apply(contract, newArgs);

        // Track emitters for cleanup on exit
        self.promiEventEmitters.push(promiEvent);

        // Subscribe to contract events / rebroadcast them to any reporters
        promiEvent
          .on("transactionHash", self._hashCb.bind(promiEvent, self, state))
          .on("receipt", self._receiptCb.bind(promiEvent, self, state));

        await self._startBlockPolling(contract.interfaceAdapter);

        // Get instance (or error)
        try {
          instance = await promiEvent;
          self._stopBlockPolling();
        } catch (err) {
          self._stopBlockPolling();
          eventArgs.error = err.error || err;
          let message = await self.emitter.emit("deployFailed", eventArgs);

          // Reporter might not be enabled (via Migrate.launchReporter) so
          // message is a (potentially empty) array of results from the emitter
          if (!message.length) {
            message = `while migrating ${contract.contractName}: ${
              eventArgs.error.message
            }`;
          }

          self.close();
          throw new Error(message);
        }

        // Case: already deployed
      } else {
        instance = await contract.deployed();
      }

      // Emit `postDeploy`
      eventArgs = {
        contract: contract,
        instance: instance,
        deployed: shouldDeploy,
        receipt: state.receipt
      };

      await self.emitter.emit("postDeploy", eventArgs);

      // Wait for `n` blocks
      if (self.confirmations !== 0 && shouldDeploy) {
        await self._waitBlocks(
          self.confirmations,
          state,
          contract.interfaceAdapter
        );
      }
      // Finish: Ensure the address and tx-hash are set on the contract.
      contract.address = instance.address;
      contract.transactionHash = instance.transactionHash;
      return instance;
    };
  }

查看frontier的源码,找到报 no signer available 的源码:

//IdeaProjects/paritytech/frontier/client/rpc/src/eth.rs

	fn send_transaction(&self, request: TransactionRequest) -> BoxFuture<H256> {
		let from = match request.from {
			Some(from) => from,
			None => {
				let accounts = match self.accounts() {
					Ok(accounts) => accounts,
					Err(e) => return Box::new(future::result(Err(e))),
				};

				match accounts.get(0) {
					Some(account) => account.clone(),
					None => return Box::new(future::result(Err(internal_err("no signer available")))),
				}
			},
		};

		let nonce = match request.nonce {
			Some(nonce) => nonce,
			None => {
				match self.transaction_count(from, None) {
					Ok(nonce) => nonce,
					Err(e) => return Box::new(future::result(Err(e))),
				}
			},
		};

		let chain_id = match self.chain_id() {
			Ok(chain_id) => chain_id,
			Err(e) => return Box::new(future::result(Err(e))),
		};

		let message = ethereum::TransactionMessage {
			nonce,
			gas_price: request.gas_price.unwrap_or(U256::from(1)),
			gas_limit: request.gas.unwrap_or(U256::max_value()),
			value: request.value.unwrap_or(U256::zero()),
			input: request.data.map(|s| s.into_vec()).unwrap_or_default(),
			action: match request.to {
				Some(to) => ethereum::TransactionAction::Call(to),
				None => ethereum::TransactionAction::Create,
			},
			chain_id: chain_id.map(|s| s.as_u64()),
		};

		let mut transaction = None;

		//寻找from地址对应的私钥,若找到则用这个私钥进行签名
		for signer in &self.signers {
			if signer.accounts().contains(&from) {
				match signer.sign(message, &from) {
					Ok(t) => transaction = Some(t),
					Err(e) => return Box::new(future::result(Err(e))),
				}
				break
			}
		}

		let transaction = match transaction {
			Some(transaction) => transaction,
			None => return Box::new(future::result(Err(internal_err("no signer available")))),
		};
		let transaction_hash = H256::from_slice(
			Keccak256::digest(&rlp::encode(&transaction)).as_slice()
		);
		let hash = self.client.info().best_hash;
		let number = self.client.info().best_number;
		let pending = self.pending_transactions.clone();
		Box::new(
			self.pool
				.submit_one(
					&BlockId::hash(hash),
					TransactionSource::Local,
					self.convert_transaction.convert_transaction(transaction.clone()),
				)
				.compat()
				.map(move |_| {
					if let Some(pending) = pending {
						if let Ok(locked) = &mut pending.lock() {
							locked.insert(
								transaction_hash,
								PendingTransaction::new(
									transaction_build(transaction, None, None),
									UniqueSaturatedInto::<u64>::unique_saturated_into(
										number
									)
								)
							);
						}
					}
					transaction_hash
				})
				.map_err(|err| internal_err(format!("submit transaction to pool failed: {:?}", err)))
		)
	}

frontier的send_transaction这个rpc接口会根据参数from(账户地址)从signers中寻找对应的私钥,然后用这个私钥签名交易。

frontier预置了一个私钥:

//IdeaProjects/paritytech/frontier/template/node/src/rpc.rs

	let mut signers = Vec::new();
	if enable_dev_signer {
		signers.push(Box::new(EthDevSigner::new()) as Box<dyn EthSigner>);
	}

EthDevSigner结构里面即保存着私钥:

pub struct EthDevSigner {
	keys: Vec<secp256k1::SecretKey>,
}

因此用truffle migrate部署合约时的from参数要填写这个预置的私钥对应的地址,否则frontier的rpc在接收到交易时对交易进行签名时会报错:no signer available,因为找不到这个地址对应的私钥。

truffle-config.js 中所有可配置的参数具体见: https://www.trufflesuite.com/docs/truffle/reference/configuration

添加from参数后,配置 truffle-config.js 内容如下:

module.exports = {

  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*",
      from: "0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A",

    }
  }   

};

这个内置的私钥对应的账户地址是:0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A,可以在钱包里导入私钥账户即可看到这个私钥对应的地址。

再次执行 truffle migrate 进行部署合约,报错:InvalidTransaction: Payment

用metamask给0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A这个账户转些费用,因为转账需要手续费。

再次执行 truffle migrate 进行部署合约:

/TruffleTest/MetaCoin$ truffle migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'development'
> Network id:      1500
> Block gas limit: 15000000 (0xe4e1c0)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x423177b44d39f5c6f55d6ed763ef85f4fea70a7bc30c898851dea4ee75906d39
   > Blocks: 0            Seconds: 0
   > contract address:    0xAE519FC2Ba8e6fFE6473195c092bF1BAe986ff90
   > block number:        360
   > block timestamp:     1628651004
   > account:             0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A
   > balance:             489.999835825
   > gas used:            164175 (0x2814f)
   > gas price:           1 gwei
   > value sent:          0 ETH
   > total cost:          0.000164175 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:         0.000164175 ETH


2_deploy_contracts.js
=====================

   Deploying 'ConvertLib'
   ----------------------
   > transaction hash:    0x53e1e737687e3cf872d3cecb1993524734c4520258c4cab5be8abb23a07e9070
   > Blocks: 0            Seconds: 4
   > contract address:    0x7d73424a8256C0b2BA245e5d5a3De8820E45F390
   > block number:        362
   > block timestamp:     1628651016
   > account:             0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A
   > balance:             489.999698014
   > gas used:            95470 (0x174ee)
   > gas price:           1 gwei
   > value sent:          0 ETH
   > total cost:          0.00009547 ETH


   Linking
   -------
   * Contract: MetaCoin <--> Library: ConvertLib (at address: 0x7d73424a8256C0b2BA245e5d5a3De8820E45F390)

   Deploying 'MetaCoin'
   --------------------
   > transaction hash:    0x8eceaa5e8290bab692bb3d5abdbd44e32a295c3f426f594ef2555fcc6aa8e524
   > Blocks: 0            Seconds: 4
   > contract address:    0x08425D9Df219f93d5763c3e85204cb5B4cE33aAa
   > block number:        363
   > block timestamp:     1628651022
   > account:             0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A
   > balance:             489.999411449
   > gas used:            286565 (0x45f65)
   > gas price:           1 gwei
   > value sent:          0 ETH
   > total cost:          0.000286565 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:         0.000382035 ETH


Summary
=======
> Total deployments:   3
> Final cost:          0.00054621 ETH

调用以太坊的RPC接口发起一笔部署合约会不会涉及到substrate账户

moonbeam 的truffle box做了哪些工作使得兼容truffle migrate

相关内容:
web3.eth.sendTransaction
web3.eth.sendTransaction

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值