




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
第python區(qū)塊鏈簡易版交易完善挖礦獎(jiǎng)勵(lì)示例目錄說明引言獎(jiǎng)勵(lì)UTXO集Merkle樹P2PKH總結(jié)
說明
本文根據(jù)/liuchengxu/blockchain-tutorial的內(nèi)容,用python實(shí)現(xiàn)的,但根據(jù)個(gè)人的理解進(jìn)行了一些修改,大量引用了原文的內(nèi)容。文章末尾有本節(jié)完整源碼實(shí)現(xiàn)地址。
引言
在這個(gè)系列文章的一開始,我們就提到了,區(qū)塊鏈?zhǔn)且粋€(gè)分布式數(shù)據(jù)庫。不過在之前的文章中,我們選擇性地跳過了分布式這個(gè)部分,而是將注意力都放到了數(shù)據(jù)庫部分。到目前為止,我們幾乎已經(jīng)實(shí)現(xiàn)了一個(gè)區(qū)塊鏈數(shù)據(jù)庫的所有元素。今天,我們將會分析之前跳過的一些機(jī)制。而在下一篇文章中,我們將會開始討論區(qū)塊鏈的分布式特性。
獎(jiǎng)勵(lì)
在上一篇文章中,我們略過的一個(gè)小細(xì)節(jié)是挖礦獎(jiǎng)勵(lì)?,F(xiàn)在,我們已經(jīng)可以來完善這個(gè)細(xì)節(jié)了。
挖礦獎(jiǎng)勵(lì),實(shí)際上就是一筆coinbase交易。當(dāng)一個(gè)挖礦節(jié)點(diǎn)開始挖出一個(gè)新塊時(shí),它會將交易從隊(duì)列中取出,并在前面附加一筆coinbase交易。coinbase交易只有一個(gè)輸出,里面包含了礦工的公鑰哈希。
實(shí)現(xiàn)獎(jiǎng)勵(lì),非常簡單,更新send即可:
defadd_block(self,transactions):
addablocktoblock_chain
last_block=self.get_last_block()
prev_hash=last_block.get_header_hash()
height=last_block.block_header.height+1
block_header=BlockHeader('',height,prev_hash)
#rewardtowallets[0]
wallets=Wallets()
keys=list(wallets.wallets.keys())
w=wallets[keys[0]]
coin_base_tx=self.coin_base_tx(w.address)
transactions.insert(0,coin_base_tx)
block=Block(block_header,transactions)
block.mine(self)
block.set_header_hash()
self.db.create(block.block_header.hash,block.serialize())
last_hash=block.block_header.hash
self.set_last_hash(last_hash)
utxo=UTXOSet()
utxo.update(block)
獎(jiǎng)勵(lì)給當(dāng)前錢包的第一個(gè)地址。
UTXO集
在Part3:持久化和命令行接口中,我們研究了BitcoinCore是如何在一個(gè)數(shù)據(jù)庫中存儲塊的,并且了解到區(qū)塊被存儲在blocks數(shù)據(jù)庫,交易輸出被存儲在chainstate數(shù)據(jù)庫。會回顧一下chainstate的機(jī)構(gòu):
c+32字節(jié)的交易哈希-該筆交易的未花費(fèi)交易輸出記錄
B+32字節(jié)的塊哈希-未花費(fèi)交易輸出的塊哈希
在之前那篇文章中,雖然我們已經(jīng)實(shí)現(xiàn)了交易,但是并沒有使用chainstate來存儲交易的輸出。所以,接下來我們繼續(xù)完成這部分。
chainstate不存儲交易。它所存儲的是UTXO集,也就是未花費(fèi)交易輸出的集合。除此以外,它還存儲了數(shù)據(jù)庫表示的未花費(fèi)交易輸出的塊哈希,不過我們會暫時(shí)略過塊哈希這一點(diǎn),因?yàn)槲覀冞€沒有用到塊高度(但是我們會在接下來的文章中繼續(xù)改進(jìn))。
那么,我們?yōu)槭裁葱枰猆TXO集呢?
來思考一下我們早先實(shí)現(xiàn)的Blockchain._find_unspent_transactions方法:
def_find_unspent_transactions(self,address):
Findallunspenttransactions
spent_txos={}
unspent_txs={}
last_block=self.get_last_block()
last_height=last_block.block_header.height
#Reverse
forheightinrange(last_height,-1,-1):
block=self.get_block_by_height(height)
fortxinblock.transactions:
txid=tx.txid
#alloutputs
forvout_index,voutinenumerate(tx.vouts):
txos=spent_txos.get(txid,[])
#vout_indexisspent
ifvout_indexintxos:
continue
ifvout.can_unlock_output_with(address):
old_vouts=unspent_txs.get(tx,[])
old_vouts.append(vout)
unspent_txs[tx]=old_vouts
ifnottx.is_coinbase():
forvinintx.vins:
ifvin.can_be_unlocked_with(address):
txid_vouts=spent_txos.get(txid,[])
txid_vouts.append(vin.vout)
spent_txos[vin.txid]=txid_vouts
returnunspent_txs
這個(gè)函數(shù)找到有未花費(fèi)輸出的交易。由于交易被保存在區(qū)塊中,所以它會對區(qū)塊鏈里面的每一個(gè)區(qū)塊進(jìn)行迭代,檢查里面的每一筆交易。截止2017年9月18日,在比特幣中已經(jīng)有485,860個(gè)塊,整個(gè)數(shù)據(jù)庫所需磁盤空間超過140Gb。這意味著一個(gè)人如果想要驗(yàn)證交易,必須要運(yùn)行一個(gè)全節(jié)點(diǎn)。此外,驗(yàn)證交易將會需要在許多塊上進(jìn)行迭代。
整個(gè)問題的解決方案是有一個(gè)僅有未花費(fèi)輸出的索引,這就是UTXO集要做的事情:這是一個(gè)從所有區(qū)塊鏈交易中構(gòu)建(對區(qū)塊進(jìn)行迭代,但是只須做一次)而來的緩存,然后用它來計(jì)算余額和驗(yàn)證新的交易。截止2017年9月,UTXO集大概有2.7Gb。
好了,讓我們來想一下實(shí)現(xiàn)UTXO集的話需要作出哪些改變。目前,找到交易用到了以下一些方法:
Blockchain._find_unspent_transactions-找到有未花費(fèi)輸出交易的主要函數(shù)。也是在這個(gè)函數(shù)里面會對所有區(qū)塊進(jìn)行迭代。Blockchain._find_spendable_outputs-這個(gè)函數(shù)用于當(dāng)一個(gè)新的交易創(chuàng)建的時(shí)候。如果找到有所需數(shù)量的輸出。使用Blockchain._find_unspent_transactions.Blockchain.find_UTXO-找到一個(gè)公鑰哈希的未花費(fèi)輸出,然后用來獲取余額。使用Blockchain._find_unspent_transactions.Blockchain.FindTransation-根據(jù)ID在區(qū)塊鏈中找到一筆交易。它會在所有塊上進(jìn)行迭代直到找到它。
可以看到,所有方法都對數(shù)據(jù)庫中的所有塊進(jìn)行迭代。但是目前我們還沒有改進(jìn)所有方法,因?yàn)閁TXO集沒法存儲所有交易,只會存儲那些有未花費(fèi)輸出的交易。因此,它無法用于Blockchain.FindTransaction。
所以,我們想要以下方法:
Blockchain.find_UTXO-通過對區(qū)塊進(jìn)行迭代找到所有未花費(fèi)輸出。UTXOSet.reindex-使用UTXO找到未花費(fèi)輸出,然后在數(shù)據(jù)庫中進(jìn)行存儲。這里就是緩存的地方。UTXOSet._find_spendable_outputs-類似Blockchain._find_spendable_outputs,但是使用UTXO集。UTXOSet.find_UTXO-類似Blockchain.find_UTXO,但是使用UTXO集。Blockchain.find_transaction跟之前一樣。
因此,從現(xiàn)在開始,兩個(gè)最常用的函數(shù)將會使用cache!來開始寫代碼吧。
classUTXOSet(Singleton):
FLAG='UTXO'
def__init__(self,db_url=':5984'):
self.db=DB(db_url)
這里使用一個(gè)FLAG來區(qū)分普通區(qū)塊和UTXO。
defreindex(self,bc):
key=self.FLAG+"l"
last_block=bc.get_last_block()
ifkeynotinself.db:
utxos=bc.find_UTXO()
fortxid,index_voutsinutxos.items():
key=self.FLAG+txid
#outs=[]
forindex_voutinindex_vouts:
vout=index_vout[1]
index=index_vout[0]
vout_dict=vout.serialize()
vout_dict.update({"index":index})
tmp_key=key+"-"+str(index)
try:
self.db.create(tmp_key,vout_dict)
exceptResourceConflictase:
print(e)
ifnotlast_block:
return
self.set_last_height(last_block.block_header.height)
else:
utxo_last_height=self.get_last_height()
last_block_height=last_block.block_header.height
foriinrange(utxo_last_height,last_block_height):
block=bc.get_block_by_height(i)
self.update(block)
這個(gè)方法首先判斷是否已經(jīng)構(gòu)建過UTXO集,如果沒有構(gòu)建過就從頭開始構(gòu)建UTXO集,如果已經(jīng)構(gòu)建過了,就把當(dāng)前UTXO的區(qū)塊至最新的區(qū)塊進(jìn)行更新。
Blockchain.FindUTXO幾乎跟Blockchain.FindUnspentTransactions一模一樣,但是現(xiàn)在它返回了一個(gè)TransactionID-TransactionOutputs的map。
現(xiàn)在,UTXO集可以用于發(fā)送幣:
deffind_spendable_outputs(self,address,amount):
utxos=self.find_utxo(address)
accumulated=0
spendable_utxos=[]
forftxoinutxos:
output=ftxo.txoutput
accumulated+=output.value
spendable_utxos.append(ftxo)
ifaccumulated=amount:
break
returnaccumulated,spendable_utxos
或者檢查余額:
deffind_utxo(self,address):
query={
"selector":{
"_id":{
"$regex":"^UTXO"
"pub_key_hash":address
docs=self.db.find(query)
utxos=[]
fordocindocs:
index=doc.get("index",None)
ifindexisNone:
continue
doc_id=doc.id
txid_index_str=doc_id.replace(self.FLAG,"")
_flag_index=txid_index_str.find("-")
txid=txid_index_str[:_flag_index]
ftxo=FullTXOutput(txid,TXOutput.deserialize(doc),index)
utxos.append(ftxo)
returnutxos
有了UTXO集,也就意味著我們的數(shù)據(jù)(交易)現(xiàn)在已經(jīng)被分開存儲:實(shí)際交易被存儲在區(qū)塊鏈中,未花費(fèi)輸出被存儲在UTXO集中。這樣一來,我們就需要一個(gè)良好的同步機(jī)制,因?yàn)槲覀兿胍猆TXO集時(shí)刻處于最新狀態(tài),并且存儲最新交易的輸出。但是我們不想每生成一個(gè)新塊,就重新生成索引,因?yàn)檫@正是我們要極力避免的頻繁區(qū)塊鏈掃描。因此,我們需要一個(gè)機(jī)制來更新UTXO集:
defupdate(self,block):
fortxinblock.transactions:
txid=tx.txid
key=self.FLAG+txid
#adduxto
forvout_index,voutinenumerate(tx.vouts):
vout_dict=vout.serialize()
vout_dict.update({"index":vout_index})
tmp_key=key+"-"+str(vout_index)
try:
self.db.create(tmp_key,vout_dict)
exceptResourceConflictase:
print(e)
#vinsdeleteusedutxo
forvinintx.vins:
vin_txid=vin.txid
key=self.FLAG+vin_txid+"-"+str(vin.vout)
doc=self.db.get(key)
ifnotdoc:
continue
try:
self.db.delete(doc)
exceptResourceNotFoundase:
print(e)
self.set_last_height(block.block_header.height)
雖然這個(gè)方法看起來有點(diǎn)復(fù)雜,但是它所要做的事情非常直觀。當(dāng)挖出一個(gè)新塊時(shí),應(yīng)該更新UTXO集。更新意味著移除已花費(fèi)輸出,并從新挖出來的交易中加入未花費(fèi)輸出。如果一筆交易的輸出被移除,并且不再包含任何輸出,那么這筆交易也應(yīng)該被移除。相當(dāng)簡單!
#創(chuàng)建創(chuàng)世塊
$python3main.py
wallet.Walletobjectat0x0000010AED8276A0wallet.Walletobjectat0x0000010AED827940
19RUj6zvbrAXNnEtkuric5pYQJTkZy57nc17AEyyKbeoEbfMa3jS8Uji6tVG37DrTJN9
Mininganewblock
Foundnonce==53ash_hex==0adfd71d90955ad9219871d8abe03ae83ef9f1f13f9a141ef6ca0ce2d16c93af
('conflict','Documentupdateconflict.')
Block(_block_header=BlockHeader(timestamp='1551246051.6814992',hash_merkle_root='1f6cf2e68e8ab0dda1cc1550f85b4df85b83db3cc3af262b26a5a306121725be',prev_block_hash='',hash='ef20a87f2edc8589e813be60d534e736f51c45a3ec94e1918c18bce057afc89d',nonce=None,height=0))
Block(_block_header=BlockHeader(timestamp='1551246052.0582814',hash_merkle_root='3cf2c8514fdaac0cb2b6502f72cf267bcf9966042be28ee48eff61e4695a90f2',prev_block_hash='ef20a87f2edc8589e813be60d534e736f51c45a3ec94e1918c18bce057afc89d',hash='b0bdedf26575722a7efdf94db7dfa60c1c4dfe1483529ff04dd553d6828de718',nonce=53,height=1))
$python3cli.pysend--from19RUj6zvbrAXNnEtkuric5pYQJTkZy57nc--to17AEyyKbeoEbfMa3jS8Uji6tVG37DrTJN9--amount10
Mininganewblock
Foundnonce==20ash_hex==07e91245d4e66b66279224980b0325c37d2f2e54a75402bdcd8fe55346cb3dcb
send10from19RUj6zvbrAXNnEtkuric5pYQJTkZy57ncto17AEyyKbeoEbfMa3jS8Uji6tVG37DrTJN9
#查詢余額
$python3cli.pybalance19RUj6zvbrAXNnEtkuric5pYQJTkZy57nc
19RUj6zvbrAXNnEtkuric5pYQJTkZy57ncbalanceis1980
一切工作正常,19RUj6zvbrAXNnEtkuric5pYQJTkZy57nc收到了創(chuàng)世塊和轉(zhuǎn)賬的獎(jiǎng)勵(lì)2000個(gè),轉(zhuǎn)賬了兩次一共使用了20個(gè),剩余1980個(gè)。
Merkle樹
在這篇文章中,我還想要再討論一個(gè)優(yōu)化機(jī)制。
上如上面所提到的,完整的比特幣數(shù)據(jù)庫(也就是區(qū)塊鏈)需要超過140Gb的磁盤空間。因?yàn)楸忍貛诺娜ブ行幕匦?,網(wǎng)絡(luò)中的每個(gè)節(jié)點(diǎn)必須是獨(dú)立,自給自足的,也就是每個(gè)節(jié)點(diǎn)必須存儲一個(gè)區(qū)塊鏈的完整副本。隨著越來越多的人使用比特幣,這條規(guī)則變得越來越難以遵守:因?yàn)椴惶赡苊總€(gè)人都去運(yùn)行一個(gè)全節(jié)點(diǎn)。并且,由于節(jié)點(diǎn)是網(wǎng)絡(luò)中的完全參與者,它們負(fù)有相關(guān)責(zé)任:節(jié)點(diǎn)必須驗(yàn)證交易和區(qū)塊。另外,要想與其他節(jié)點(diǎn)交互和下載新塊,也有一定的網(wǎng)絡(luò)流量需求。
在中本聰?shù)谋忍貛旁颊撐闹?,對這個(gè)問題也有一個(gè)解決方案:簡易支付驗(yàn)證(SimplifiedPaymentVerification,SPV)。SPV是一個(gè)比特幣輕節(jié)點(diǎn),它不需要下載整個(gè)區(qū)塊鏈,也不需要驗(yàn)證區(qū)塊和交易。相反,它會在區(qū)塊鏈查找交易(為了驗(yàn)證支付),并且需要連接到一個(gè)全節(jié)點(diǎn)來檢索必要的數(shù)據(jù)。這個(gè)機(jī)制允許在僅運(yùn)行一個(gè)全節(jié)點(diǎn)的情況下有多個(gè)輕錢包。
為了實(shí)現(xiàn)SPV,需要有一個(gè)方式來檢查是否一個(gè)區(qū)塊包含了某筆交易,而無須下載整個(gè)區(qū)塊。這就是Merkle樹所要完成的事情。
比特幣用Merkle樹來獲取交易哈希,哈希被保存在區(qū)塊頭中,并會用于工作量證明系統(tǒng)。到目前為止,我們只是將一個(gè)塊里面的每筆交易哈希連接了起來,將在上面應(yīng)用了SHA-256算法。雖然這是一個(gè)用于獲取區(qū)塊交易唯一表示的一個(gè)不錯(cuò)的途徑,但是它沒有利用到Merkle樹。
來看一下Merkle樹:
每個(gè)塊都會有一個(gè)Merkle樹,它從葉子節(jié)點(diǎn)(樹的底部)開始,一個(gè)葉子節(jié)點(diǎn)就是一個(gè)交易哈希(比特幣使用雙SHA256哈希)。葉子節(jié)點(diǎn)的數(shù)量如果不是雙數(shù),就只取單個(gè)數(shù)據(jù)的hash。
從下往上,兩兩成對,連接兩個(gè)節(jié)點(diǎn)哈希,將組合哈希作為新的哈希。新的哈希就成為新的樹節(jié)點(diǎn)。重復(fù)該過程,直到僅有一個(gè)節(jié)點(diǎn),也就是樹根。根哈希然后就會當(dāng)做是整個(gè)塊交易的唯一標(biāo)示,將它保存到區(qū)塊頭,然后用于工作量證明。
Merkle樹的好處就是一個(gè)節(jié)點(diǎn)可以在不下載整個(gè)塊的情況下,驗(yàn)證是否包含某筆交易。并且這些只需要一個(gè)交易哈希,一個(gè)Merkle樹根哈希和一個(gè)Merkle路徑。
這部分的描述和/liuchengxu/blockchain-tutorial/blob/master/content/part-6/transactions-2.md描述有所不同,
因?yàn)榇嬖谌~子節(jié)點(diǎn)為雙數(shù),但是第二層為單數(shù)的情況,會導(dǎo)致原版代碼出現(xiàn)索引越界的情況。這部分的描述參考/blockchain_guide/crypto/merkle_trie.html
實(shí)現(xiàn)代碼如下:
classMerkleNode(object):
def__init__(self,left_node,right_node,data):
self.left=left_node
self.right=right_node
ifnotself.leftandnotself.right:
self.data=sum256_hex(data)
else:
data=self.left.data+self.right.data
self.data=sum256_hex(data)
classMerkleTree(object):
def__init__(self,datas):
nodes=[]
fordata_itemindatas:
node=MerkleNode(None,None,data_item)
nodes.append(node)
for_inrange(len(datas)//2):
new_level=[]
forjinrange(0,len(nodes),2):
ifj+1=len(nodes):
node=MerkleNode(nodes[j],"",None)
else:
node=MerkleNode(nodes[j],nodes[j+1],None)
new_level.append(node)
nodes=new_level
self.root_node=nodes[0]
@property
defroot_hash(self):
returnself.root_node.data
如果最后只有單個(gè)節(jié)點(diǎn),那么就將另一個(gè)數(shù)據(jù)置空,只計(jì)算一個(gè)數(shù)據(jù)的哈希。
ifj+1=len(nodes):
node=MerkleNode(nodes[j],"",None)
根節(jié)點(diǎn)的data域就是哈希。
@property
defroot_hash(self):
returnself.root_node.data
P2PKH
還有一件事情,我想要再談一談。
大家應(yīng)該還記得,在比特幣中有一個(gè)*腳本(Script)*編程語言,它用于鎖定交易輸出;交易輸入提供了解鎖輸出的數(shù)據(jù)。這個(gè)語言非常簡單,用這個(gè)語言寫的代碼其實(shí)就是一系列數(shù)據(jù)和操作符而已。比如如下示例:
52OP_ADD7OP_EQUAL
5,2,和7是數(shù)據(jù),OP_ADD和OP_EQUAL是操作符。腳本代碼從左到右執(zhí)行:將數(shù)據(jù)依次放入棧內(nèi),當(dāng)遇到操作符時(shí),就從棧內(nèi)取出數(shù)據(jù),并將操作符作用于數(shù)據(jù),然后將結(jié)果作為棧頂元素。腳本的棧,實(shí)際上就是一個(gè)先進(jìn)后出的內(nèi)存存儲:棧里的第一個(gè)元素最后一個(gè)取出,后面的每一個(gè)元素都會放到前一個(gè)元素之上。
讓我們來對上面的腳本分部執(zhí)行:
步驟棧腳本說明1空52OP_ADD7OP_EQUAL一開始棧為空252OP_ADD7OP_EQUAL從腳本里面取出5放入棧上352OP_ADD7OP_EQUAL從腳本里面取出2放入棧上477OP_EQUAL遇到操作符OP_ADD,從棧里取出兩個(gè)操作數(shù)5和2,相加后將結(jié)果放回棧上577OP_EQUAL從腳本里面取出7放到棧上6true空遇到操作符OP_EQUAL,從棧里取出兩個(gè)操作數(shù)并比較,將比較的結(jié)果放回棧內(nèi),腳本執(zhí)行完畢,為空
OP_ADD從棧內(nèi)取兩個(gè)元素,將這兩個(gè)元素進(jìn)行相加,然后將結(jié)果重新放回棧內(nèi)。OP_EQUAL從棧內(nèi)取兩個(gè)元素,然后對這兩個(gè)元素進(jìn)行比較:如果它們相等,就在棧上放一個(gè)true,否則放一個(gè)false。腳本執(zhí)行的結(jié)果就是棧頂元素:在我們的案例中,如果是true,那么表明腳本執(zhí)行成功。
現(xiàn)在來看一下在比特幣中,是如何用腳本執(zhí)行支付的:
signaturepubKeyOP_DUPOP_HASH160pubKeyHashOP_EQUALVERIFYOP_CHECKSIG
這個(gè)腳本叫做PaytoPublicKeyHash(P2PKH),這是比特幣最常用的一個(gè)腳本。它所做的事情就是
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 任務(wù)保證金合同范例
- 全額購房合同范例
- 2025年上半年中學(xué)教師工作總結(jié)模版
- 買地協(xié)議合同范例
- 仿古門窗供貨合同范例
- 人教版物理八年級下冊知識點(diǎn)總結(jié)模版
- 上海施工合同范例2004
- 醫(yī)療行業(yè)辦公用品供應(yīng)鏈的優(yōu)化
- 區(qū)塊鏈賦能智慧城市建設(shè)的十個(gè)方向
- l禮盒銷售合同范例
- 2024年上海市高考英語完形填空試題真題匯編(含答案詳解)
- 慢阻肺病人健康宣教課件
- 湯顯祖《牡丹亭·游園》品讀課件
- 物業(yè)安全生產(chǎn)培訓(xùn)
- 煤礦反三違培訓(xùn)課件
- 六年級下冊《生命.生態(tài).安全》全冊教案
- 人工智能在影視后期制作中的應(yīng)用
- 關(guān)于粵語傳承與保護(hù)現(xiàn)狀的調(diào)查報(bào)告
- JGT491-2016 建筑用網(wǎng)格式金屬電纜橋架
- 網(wǎng)絡(luò)設(shè)備巡檢表
- 阿那亞分析研究報(bào)告
評論
0/150
提交評論