源码分析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