iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Web 3

從 區塊鏈 到 去中心化應用程式(DApp)系列 第 14

區塊鏈建立: 區塊運算 之 交易的流向

  • 分享至 

  • xImage
  •  

區塊運算 之 交易的流向

專案 GitHub 位置: https://github.com/weiawesome/block-chain_go

基本上開始進入 對於區塊中計算的環節

首先要面對的便是 交易的處理

交易從進到節點 到交易進去區塊計算的細節為何
這便是今日實現的重點

因此此篇根據以下幾點進行敘述

  1. 交易的驗證
  2. 從交易池中 選擇並構成區塊

完成的範圍便是 接受驗證交易刷新區塊

接受驗證交易

Channel 名稱 Channel 類型 地點
TransactionChannel 廣播系統
BroadcastTransactionChannel 廣播系統
BlockTransactionChannel 刷新區塊

刷新區塊

Channel 名稱 Channel 類型 地點
BlockTransactionChannel 接受驗證交易
CompleteBlockChannel 刷新資料庫
MinersBlockChannel 礦工管理者

交易的驗證

檔案位置: "./service/receive_validate_transaction/receive_validate_transaction.go"

func ReceiveValidateTransaction(TransactionChannel chan transaction.Transaction, BroadcastTransactionChannel chan transaction.Transaction, BlockTransactionChannel chan blockchain.BlockTransaction) {
	for {
		select {
		case t := <-TransactionChannel:
			block, err := block_control.GetLastBlock()
			if err != nil {
				continue
			}
			if blockdb.TransactionInCheckedBlocks(block, t.TransactionHash) {
				StringValue, err := t.ToString()
				if err != nil {
					continue
				}
				key, err := utils.DecodePublicKey(t.PublicKey)
				if err != nil {
					continue
				}
				address := utils.GetAddress(conseous.VersionPrefix, key)
				if utils.Verify(t.Signature, key, StringValue) {
					sumOfHave := float64(0)
					flag := false
					for _, from := range t.From {
						value, err := utxo.GetUTXO(from.UTXOHash, from.Index)
						if err != nil || value.Spent == true {
							continue
						}
						if address != value.Address {
							flag = true
							break
						}
						sumOfHave += value.Amount
					}
					if flag {
						continue
					}
					sumOfSpent := float64(0)
					for _, to := range t.To {
						sumOfSpent += to.Amount
					}
					sumOfSpent += t.Fee

					if sumOfSpent > sumOfHave {
						continue
					}
					BroadcastTransactionChannel <- t
					BlockTransactionChannel <- transactionUtils.ConvertTransaction(t)
				}
			}
		default:
			continue
		}
	}
}
  1. 首先 先獲得最後區塊哈希值
  2. 確認附近區塊 有無相同交易(避免雙重花費
  3. 獲取公鑰 進行簽名確認
  4. 進行確認 擁有的金額是否大於等於手續費加支出金額
  5. 上述都沒問題 廣播出去 與 交給區塊生成

至於公私鑰相關 簽名驗證方法
檔案位置: "./utils/ecc.go"

type SignatureData struct {
	R *big.Int `json:"r"`
	S *big.Int `json:"s"`
}

type KeyPair struct {
	PublicKey  string `json:"public_key"`
	PrivateKey string `json:"private_key"`
}

func GenerateKeyPair() (KeyPair, error) {
	curve := elliptic.P256()
	privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
	if err != nil {
		return KeyPair{}, err
	}
	privateKeyBytes := privateKey.D.Bytes()
	privateKeyBase64 := base64.StdEncoding.EncodeToString(privateKeyBytes)

	publicKey := &privateKey.PublicKey
	publicKeyBytes := elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
	publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKeyBytes)

	return KeyPair{PublicKey: publicKeyBase64, PrivateKey: privateKeyBase64}, err
}

func DecodePublicKey(PublicKey string) (*ecdsa.PublicKey, error) {
	curve := elliptic.P256()

	decodedPublicKeyBytes, err := base64.StdEncoding.DecodeString(PublicKey)
	if err != nil {
		return &ecdsa.PublicKey{}, err
	}

	decodedPublicKey := new(ecdsa.PublicKey)
	decodedPublicKey.Curve = curve
	decodedPublicKey.X, decodedPublicKey.Y = elliptic.Unmarshal(curve, decodedPublicKeyBytes)

	return decodedPublicKey, nil
}

func DecodePrivateKey(PrivateKey string) (*ecdsa.PrivateKey, error) {
	curve := elliptic.P256()
	decodedPrivateKeyBytes, err := base64.StdEncoding.DecodeString(PrivateKey)
	if err != nil {
		return &ecdsa.PrivateKey{}, err
	}

	decodedPrivateKey := new(ecdsa.PrivateKey)
	decodedPrivateKey.Curve = curve
	decodedPrivateKey.D = new(big.Int).SetBytes(decodedPrivateKeyBytes)

	return decodedPrivateKey, nil
}

func Signature(Content string, PrivateKey *ecdsa.PrivateKey) (string, error) {
	ContentBytes := []byte(Content)
	r, s, err := ecdsa.Sign(rand.Reader, PrivateKey, ContentBytes)
	if err != nil {
		return "", err
	}
	jsonData, err := json.Marshal(SignatureData{R: r, S: s})
	if err != nil {
		return "", err
	}
	return string(jsonData), nil
}

func Verify(Signature string, PublicKey *ecdsa.PublicKey, Content string) bool {
	ContentBytes := []byte(Content)
	var signature SignatureData

	err := json.Unmarshal([]byte(Signature), &signature)
	if err != nil {
		return false
	}

	valid := ecdsa.Verify(PublicKey, ContentBytes, signature.R, signature.S)

	return valid
}

包含生成、解碼、簽名、驗證 各種方法都有

從交易池中 選擇並構成區塊

首先 何為交易池

基本上 就是一個優先佇列(Priority Queue)
當一筆交易的手續費越高
則處理的優先順序越高

檔案位置: "./utils/priority_queue.go"

type PriorityQueue []*blockchain.BlockTransaction

func (pq *PriorityQueue) Len() int { return len(*pq) }

func (pq *PriorityQueue) Less(i, j int) bool {
	return (*pq)[i].Fee < (*pq)[j].Fee
}

func (pq *PriorityQueue) Swap(i, j int) {
	(*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i]
}

func (pq *PriorityQueue) Push(x interface{}) {
	item := x.(*blockchain.BlockTransaction)
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	*pq = old[0 : n-1]
	return item
}

如此一來 當確認過的交易 先丟進優先佇列當中
並逐漸挑選出 手續費較高的交易

檔案位置: "./protocal/refresh_block/refresh_block.go"

func RefreshBlock(BlockTransactionChannel chan blockchain.BlockTransaction, MinersBlockChannel chan blockchain.Block, CompleteBlockChannel chan blockchain.Block) {
	TransactionPool := make(utils.PriorityQueue, 0)
	var TargetBlock blockchain.Block
	key, err := utils.DecodePublicKey(utils.GetPublicKey())
	if err != nil {
		return
	}
	address := utils.GetAddress(conseous.VersionPrefix, key)
	minerTransaction, err := transactionUtils.MinerTransaction(address)
	if err != nil {
		return
	}
	for {
		select {
		case bt := <-BlockTransactionChannel:
			heap.Push(&TransactionPool, bt)
			var tmpBlock blockchain.Block
			tmpBlock.BlockTransactions = append(tmpBlock.BlockTransactions, minerTransaction)
			tmpTransactionPool := TransactionPool
			for tmpTransactionPool.Len() > 0 {
				t := heap.Pop(&tmpTransactionPool).(*blockchain.BlockTransaction)
				tmpBlock.BlockTransactions = append(tmpBlock.BlockTransactions, *t)
				if unsafe.Sizeof(tmpBlock) > conseous.BlockSize {
					tmpBlock.BlockTransactions = tmpBlock.BlockTransactions[:len(tmpBlock.BlockTransactions)-1]
					break
				}
			}
			tmpBlock.ComputeMarkleRoot()
			if tmpBlock.BlockTop.MarkleRoot != TargetBlock.BlockTop.MarkleRoot {
				TargetBlock = tmpBlock
				TargetBlock.BlockTop.Version = conseous.Version
				TargetBlock.BlockTop.TimeStamp = time.Now().Unix()
				blockHash, err := block_control.GetLastBlock()
				if err != nil {
					blockHash = conseous.GenesisBlockPreviousHash
					TargetBlock.BlockMeta = blockchain.BlockMeta{Content: conseous.GenesisBlockContent}
					TargetBlock.BlockTop.BlockHeight = conseous.GenesisBlockHeight
					TargetBlock.BlockTop.Difficulty = conseous.GenesisBlockDifficulty
				} else {
					b, err := block.GetBlock(blockHash)
					if err != nil {
						continue
					}
					TargetBlock.BlockTop.BlockHeight = b.BlockTop.BlockHeight + 1
					TargetBlock.BlockTop.Difficulty = b.BlockTop.Difficulty
					if TargetBlock.BlockTop.BlockHeight%conseous.DifficultyCycle == 0 {
						speed, err := block.CheckGenerateSpeed(TargetBlock.BlockTop.BlockHeight)
						if err == nil {
							if speed {
								if TargetBlock.BlockTop.Difficulty > conseous.DifficultyLower {
									TargetBlock.BlockTop.Difficulty -= 1
								}
							} else {
								if TargetBlock.BlockTop.Difficulty < conseous.DifficultyUpper {
									TargetBlock.BlockTop.Difficulty += 1
								}
							}
						}
					}
				}
				TargetBlock.BlockTop.PreviousHash = blockHash
				MinersBlockChannel <- TargetBlock
			}
		case cb := <-CompleteBlockChannel:
			continue
		default:
			continue
		}
	}
}

目前CompleteBlockChannel部分內容先省略

  • 程序開始時 先計算節點的 礦工交易
  1. 當新確認交易出現時
  2. 將交易加入交易池當中
  3. 持續添加至區塊當中 直到沒有交易 或是 區塊容量過大
  4. 確認區塊是否變化 有變化則填充其他資訊
  5. 最後將新區塊 傳給 礦工管理者

計算礦工交易時必須先知道 礦工的地址
而礦工的地址與公鑰息息相關

檔案位置: "./utils/address.go"

func GetAddress(networkPrefix byte, publicKey *ecdsa.PublicKey) string {
	publicKeyHash := publicKeyHash(publicKey)
	payload := append([]byte{networkPrefix}, publicKeyHash...)
	checksum := checksum(payload)
	payload = append(payload, checksum...)
	address := base58Encode(payload)
	return address
}

func publicKeyHash(publicKey *ecdsa.PublicKey) []byte {
	pubKeyBytes := elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
	sha256Hash := sha256.Sum256(pubKeyBytes)
	h := ripemd160.New()
	h.Write(sha256Hash[:])
	publicKeyHash := h.Sum(nil)
	return publicKeyHash
}

func checksum(payload []byte) []byte {
	firstSHA := sha256.Sum256(payload)
	secondSHA := sha256.Sum256(firstSHA[:])
	return secondSHA[:4]
}

func base58Encode(input []byte) string {
	base58Alphabet := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

	var result []byte

	x := new(big.Int).SetBytes(input)
	base := big.NewInt(58)
	zero := big.NewInt(0)

	for x.Cmp(zero) > 0 {
		mod := new(big.Int)
		x.DivMod(x, base, mod)
		result = append([]byte{base58Alphabet[mod.Int64()]}, result...)
	}

	for _, b := range input {
		if b == 0x00 {
			result = append([]byte{base58Alphabet[0]}, result...)
		} else {
			break
		}
	}

	return string(result)
}

公鑰轉換地址步驟

  1. 首先將公鑰內容哈希
  2. 加入基本資訊
  3. 確認內容 與 添加確認資訊
  4. 重新進行編碼

當中在設定時區塊難度時 可以根據平均區塊計算時間去變化

那基本上也屬於區塊鏈協定的一環 不同的區塊鏈規定都不同
在此處可以將相關設定參數 都設定好

檔案位置: "./protocal/conseous/parameters.go"

const (
	BlockChecked             = 6
	DifficultyCycle          = 120
	AverageBlockGenerateTime = 60 * 30
	Version                  = 1
	BlockSize                = 10 * 1024 * 1024
	GenesisBlockPreviousHash = ""
	GenesisBlockContent      = "This is the block-chain made by Tcweeei!"
	GenesisBlockHeight       = 0
	GenesisBlockDifficulty   = 1
	VersionPrefix            = 0x00
	DifficultyLower          = 0
	DifficultyUpper          = 256
	MineEmpty                = false
)

包含各種基本設定的資訊

結言

基本上 對於交易進來的順序應該很清晰了

交易會遇到甚麼關卡檢驗 甚麼確認

而當中交易池的概念也很重要
(畢竟工作效率還是高一點好、拿工資(手續費)高的交易做)

希望透過這篇能夠理解

  1. 交易經過節點的流程
  2. 交易被檢驗需經過的關卡
  3. 交易池的概念
  4. 區塊計算 與 區塊難度變化

下回預告

馬不停蹄的繼續前進~~~

衝阿!!! 處理完交易 下個步驟當然是 區塊

下回 "區塊鏈建立: 區塊運算 之 區塊的流向"


上一篇
區塊鏈建立: 連線協定 與 初始化設定
下一篇
區塊鏈建立: 區塊運算 之 區塊的流向
系列文
從 區塊鏈 到 去中心化應用程式(DApp)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言