ChainMaker.go
package service
import (
"fmt"
"log"
"os"
"os/exec"
"sort"
"strings"
"time"
"saseul/config"
"saseul/data/bunch"
"saseul/data/chain"
"saseul/data/env"
"saseul/data/mainchain"
"saseul/data/tracker"
"saseul/datasource/poolclient"
"saseul/model"
"saseul/staff/observer"
"saseul/staff/processmanager"
"saseul/util/clock"
"saseul/vm/machine"
)
type ChainMaker struct {
Description string
Iterate int
}
// NewChainMaker ํจ์๋ ChainMaker์ ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ์ด๊ธฐํํฉ๋๋ค.
func NewChainMaker() *ChainMaker {
cm := &ChainMaker{
Description: "ChainMaker service",
Iterate: 100000,
}
// ํ๊ฒฝ์ด "process"์ธ ๊ฒฝ์ฐ, ์ฒด์ธ ๋ฉ์ด์ปค ํ๋ก์ธ์ค๊ฐ ์ด๋ฏธ ์คํ ์ค์ธ์ง ํ์ธํ๊ณ ์คํ ์ค์ด๋ฉด ์ข
๋ฃํฉ๋๋ค.
if config.Environment == "process" {
if processmanager.IsRunning(processmanager.CHAIN_MAKER) {
log.Println("The chain maker process is already running.")
os.Exit(1)
}
processmanager.Save(processmanager.CHAIN_MAKER)
}
// Pool ํด๋ผ์ด์ธํธ๋ฅผ "rewind" ๋ชจ๋๋ก ์ค์ ํฉ๋๋ค.
poolclient.Instance().SetMode("rewind")
return cm
}
// Main ํจ์๋ ChainMaker์ ๋ฉ์ธ ์์
์ ์ํํฉ๋๋ค.
func (cm *ChainMaker) Main() {
defer func() {
if processmanager.PID(processmanager.CHAIN_MAKER) == os.Getpid() {
log.Println("Chain maker process has been successfully removed.")
processmanager.Delete(processmanager.CHAIN_MAKER)
}
}()
cm.Init()
for i := 0; i < cm.Iterate; i++ {
cm.Operation()
time.Sleep(1 * time.Second) // ํ์ํ ๊ฒฝ์ฐ ์กฐ์
}
}
// Init ํจ์๋ ChainMaker๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
func (cm *ChainMaker) Init() {
log.Println("The chain maker process has started.")
}
// Operation ํจ์๋ ChainMaker์ ์ฃผ์ ์์
์ ์ํํฉ๋๋ค.
func (cm *ChainMaker) Operation() {
lastBlock := mainchain.Instance().LastBlock()
cm.Refresh(lastBlock)
var errMsg string
sync := cm.Sync(lastBlock, &errMsg)
if !sync {
cm.Consensus(lastBlock, &errMsg)
}
}
// Refresh ํจ์๋ ์ต์ ๋ธ๋ก์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฆฌํฉ๋๋ค.
func (cm *ChainMaker) Refresh(lastBlock *model.MainBlock) {
bunch.Clean(lastBlock)
info := bunch.InfoTxs()
count := info["count"].(int)
size := info["size"].(int)
countLimit := config.BLOCK_TX_COUNT_LIMIT * 2
sizeLimit := config.BLOCK_TX_SIZE_LIMIT * 2
if count > countLimit || size > sizeLimit {
poolclient.Instance().FlushTxs()
}
}
// Sync ํจ์๋ ๋ธ๋ก ๋๊ธฐํ๋ฅผ ์ํํฉ๋๋ค.
func (cm *ChainMaker) Sync(lastBlock *model.MainBlock, errMsg *string) bool {
peers := tracker.GetPeers()
hosts := cm.Hosts(peers)
syncHosts := cm.SyncHosts(hosts)
hosts = syncHosts["hosts"].([]string)
longest := syncHosts["longest"].(map[string]interface{})
decisions := observer.Instance().SeeBlocks(hosts, lastBlock.Height+1)
var pull bool
for _, decision := range decisions {
if !pull {
blocks := decision["blocks"].([]interface{})
isLongest := decision["host"] == longest["host"]
if cm.Pull(blocks, isLongest, errMsg) {
pull = true
}
}
}
return pull
}
// Hosts ํจ์๋ ํผ์ด ๋ชฉ๋ก์์ ํธ์คํธ ๋ชฉ๋ก์ ์ถ์ถํฉ๋๋ค.
func (cm *ChainMaker) Hosts(peers []map[string]interface{}) []string {
hosts := make([]string, len(peers))
for i, peer := range peers {
hosts[i] = peer["host"].(string)
}
return hosts
}
// SyncHosts ํจ์๋ ๋๊ธฐํํ ํธ์คํธ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
func (cm *ChainMaker) SyncHosts(hosts []string) map[string]interface{} {
height := max(chain.FixedHeight(), 1)
rounds := observer.Instance().SeeMiningRounds(hosts, height)
var longest map[string]interface{}
var maxSyncLimit int
var maxHash string
var syncHosts []string
for hash, cases := range rounds {
for _, round := range cases {
syncLimit := round["sync_limit"].(int)
if syncLimit > maxSyncLimit {
longest = round
maxSyncLimit = syncLimit
maxHash = hash
}
}
}
if longestHost, ok := longest["host"]; ok {
syncHosts = append(syncHosts, longestHost.(string))
}
for hash, cases := range rounds {
sort.SliceStable(cases, func(i, j int) bool {
if cases[i]["sync_limit"].(int) == cases[j]["sync_limit"].(int) {
return cases[i]["exec_time"].(int) < cases[j]["exec_time"].(int)
}
return cases[i]["sync_limit"].(int) > cases[j]["sync_limit"].(int)
})
syncHosts = append(syncHosts, cases[0]["host"].(string))
if len(cases) > 1 {
if hash == maxHash {
for i := 0; i < 3; i++ {
j := randInt(1, len(cases)-1)
syncHosts = append(syncHosts, cases[j]["host"].(string))
}
} else {
j := randInt(1, len(cases)-1)
syncHosts = append(syncHosts, cases[j]["host"].(string))
}
}
}
return map[string]interface{}{
"longest": longest,
"hosts": uniqueStrings(syncHosts),
}
}
// Pull ํจ์๋ ๋ธ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ์ถ๊ฐํฉ๋๋ค.
func (cm *ChainMaker) Pull(blocks []interface{}, isLongest bool, errMsg *string) bool {
var result bool
for _, blockData := range blocks {
block := model.NewMainBlock(blockData.(map[string]interface{}))
if !cm.Append(block, isLongest, errMsg) {
break
}
result = true
}
return result
}
// Append ํจ์๋ ๋ธ๋ก์ ์ถ๊ฐํฉ๋๋ค.
func (cm *ChainMaker) Append(block *model.MainBlock, isLongest bool, errMsg *string) bool {
machine := machine.Instance()
lastBlock := mainchain.Instance().LastBlock()
confirmedHeight := chain.ConfirmedHeight(block.STimestamp)
confirmedHeight = hardfork.ConfirmedHeight(block, confirmedHeight)
validators := chain.SelectValidators(confirmedHeight)
if block.Height != lastBlock.Height+1 || len(validators) == 0 {
*errMsg = "Waiting for resource blocks.. "
poolclient.Instance().SetPolicy("main_chain_waiting", false)
return true
}
block.Validators = validators
machine.Init(lastBlock, block.STimestamp)
machine.SetTransactions(block.Transactions)
machine.PreLoad(block.UniversalUpdates, block.LocalUpdates)
machine.PreCommit(confirmedHeight, errMsg)
expectedBlock := machine.ExpectedBlock()
validity := block.Validity() && (hardfork.MainCondition(block) || len(machine.Transactions) > 0) &&
block.Blockhash == expectedBlock.Blockhash
continuity := block.Height == lastBlock.Height+1 &&
block.PreviousBlockhash == lastBlock.Blockhash &&
block.STimestamp >= lastBlock.STimestamp+config.MAIN_CHAIN_INTERVAL
if validity && continuity {
poolclient.Instance().SetPolicy("main_chain_waiting", false)
machine.Commit(block)
return true
} else if !continuity && isLongest {
*errMsg = "Main chain fork."
log.Println(*errMsg)
poolclient.Instance().SetPolicy("main_chain_waiting", true)
return false
}
*errMsg = "Blockhash is different."
log.Println(*errMsg)
return false
}
// Consensus ํจ์๋ ํฉ์ ์์
์ ์ํํฉ๋๋ค.
func (cm *ChainMaker) Consensus(lastBlock *model.MainBlock, errMsg *string) {
roundKey := lastBlock.Blockhash
chunks := bunch.ListChunk(roundKey)
now := clock.UFloorTime()
min := max(now-config.REFRESH_INTERVAL, lastBlock.STimestamp)
roundTimestamp := now
refresh := false
if len(chunks) > 0 {
roundTimestamp = maxInt(chunksTimes(chunks))
if roundTimestamp < min {
refresh = true
roundTimestamp = now
}
}
transactions := bunch.ListTx(roundTimestamp)
if len(transactions) == 0 {
return
}
confirmedHeight := chain.ConfirmedHeight(roundTimestamp)
validators := chain.SelectValidators(confirmedHeight)
main := validators[8]
address := env.Node().Address()
if !contains(validators, address) || lastBlock.STimestamp >= roundTimestamp {
return
}
validChunks := filterChunks(chunks, validators)
machine := machine.Instance()
machine.Init(lastBlock, roundTimestamp)
machine.SetTransactions(transactions)
machine.PreCommit(confirmedHeight, errMsg)
chunk := machine.Chunk()
chunkHashes := getChunkHashes(validChunks)
chunkHashes[address] = chunk.ChunkHash
hypothesis := machine.Hypothesis(chunkHashes)
expectedBlock := machine.ExpectedBlock()
expectedBlock.Validators = validators
if !hardfork.MainCondition(expectedBlock) && machine.TransactionCount() == 0 {
return
}
if refresh || !chunkExists(validChunks, address) {
bunch.AddChunk(chunk)
}
if address == main {
bunch.AddHypothesis(hypothesis)
hypotheses := bunch.ListHypothesis(lastBlock.Blockhash)
cm.commitExpectedBlock(expectedBlock, hypotheses, lastBlock, validators)
} else {
if !cm.Votes(validators, getChunkKeys(validChunks)) {
return
}
bunch.AddHypothesis(hypothesis)
hypotheses := bunch.ListHypothesis(lastBlock.Blockhash)
if !cm.Votes(validators, getHypothesisKeys(hypotheses)) {
return
}
}
}
// commitExpectedBlock ํจ์๋ ์์ ๋ธ๋ก์ ์ปค๋ฐํฉ๋๋ค.
func (cm *ChainMaker) commitExpectedBlock(expectedBlock *model.MainBlock, hypotheses []map[string]interface{}, lastBlock *model.MainBlock, validators []string) {
expectedBlock = cm.Seal(expectedBlock, hypotheses)
validity := expectedBlock.Validity() &&
expectedBlock.Height == lastBlock.Height+1 &&
(hardfork.MainCondition(expectedBlock) || len(expectedBlock.Transactions) > 0)
continuity := expectedBlock.PreviousBlockhash == lastBlock.Blockhash &&
expectedBlock.STimestamp >= lastBlock.STimestamp+config.MAIN_CHAIN_INTERVAL
if validity && continuity {
machine.Instance().Commit(expectedBlock)
}
}
// Seal ํจ์๋ ์์ ๋ธ๋ก์ ๋ด์ธํฉ๋๋ค.
func (cm *ChainMaker) Seal(expectedBlock *model.MainBlock, hypotheses []map[string]interface{}) *model.MainBlock {
sealedSTimestamp := maxInt(hypothesesTimes(hypotheses))
sealedHypotheses := sealHypotheses(hypotheses)
expectedBlock.Seal = sealedHypotheses
expectedBlock.STimestamp = sealedSTimestamp
return expectedBlock
}
// Votes ํจ์๋ ํฌํ๋ฅผ ์ง๊ณํฉ๋๋ค.
func (cm *ChainMaker) Votes(validators, datas []string) bool {
quorum := len(validators) * config.MAIN_CONSENSUS_PER
votesCast := countVotes(validators, datas)
return votesCast > quorum
}
// ๋ถ๊ฐ ํจ์๋ค
// contains ํจ์๋ ์ฌ๋ผ์ด์ค์ ํญ๋ชฉ์ด ํฌํจ๋์ด ์๋์ง ํ์ธํฉ๋๋ค.
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// uniqueStrings ํจ์๋ ์ฌ๋ผ์ด์ค์์ ๊ณ ์ ํ ๋ฌธ์์ด์ ๋ฐํํฉ๋๋ค.
func uniqueStrings(input []string) []string {
unique := make(map[string]struct{}, len(input))
for _, str := range input {
unique[str] = struct{}{}
}
output := make([]string, 0, len(unique))
for str := range unique {
output = append(output, str)
}
return output
}
// max ํจ์๋ ๋ ์ ์ ์ค ๋ ํฐ ๊ฐ์ ๋ฐํํฉ๋๋ค.
func max(a, b int) int {
if a > b {
return a
}
return b
}
// maxInt ํจ์๋ ์ ์ ์ฌ๋ผ์ด์ค์์ ๊ฐ์ฅ ํฐ ๊ฐ์ ๋ฐํํฉ๋๋ค.
func maxInt(ints []int) int {
if len(ints) == 0 {
return 0
}
max := ints[0]
for _, v := range ints {
if v > max {
max = v
}
}
return max
}
// randInt ํจ์๋ ์ฃผ์ด์ง ๋ฒ์ ๋ด์์ ๋ฌด์์ ์ ์๋ฅผ ๋ฐํํฉ๋๋ค.
func randInt(min, max int) int {
return min + int(rand.Int31n(int32(max-min+1)))
}
// chunksTimes ํจ์๋ ์ฒญํฌ์ ํ์์คํฌํ๋ฅผ ๋ฐํํฉ๋๋ค.
func chunksTimes(chunks []map[string]interface{}) []int {
times := make([]int, len(chunks))
for i, chunk := range chunks {
times[i] = chunk["s_timestamp"].(int)
}
return times
}
// getChunkHashes ํจ์๋ ์ฒญํฌ์ ํด์๋ฅผ ๋ฐํํฉ๋๋ค.
func getChunkHashes(chunks map[string]map[string]interface{}) map[string]string {
hashes := make(map[string]string, len(chunks))
for key, chunk := range chunks {
hashes[key] = chunk["chunk_hash"].(string)
}
return hashes
}
// getChunkKeys ํจ์๋ ์ฒญํฌ์ ํค๋ฅผ ๋ฐํํฉ๋๋ค.
func getChunkKeys(chunks map[string]map[string]interface{}) []string {
keys := make([]string, 0, len(chunks))
for key := range chunks {
keys = append(keys, key)
}
return keys
}
// chunkExists ํจ์๋ ์ฒญํฌ๊ฐ ์กด์ฌํ๋์ง ํ์ธํฉ๋๋ค.
func chunkExists(chunks map[string]map[string]interface{}, address string) bool {
_, exists := chunks[address]
return exists
}
// filterChunks ํจ์๋ ๊ฒ์ฆ์ ๋ชฉ๋ก์ ๋ฐ๋ผ ์ ํจํ ์ฒญํฌ๋ฅผ ํํฐ๋งํฉ๋๋ค.
func filterChunks(chunks []map[string]interface{}, validators []string) map[string]map[string]interface{} {
validChunks := make(map[string]map[string]interface{})
for _, chunk := range chunks {
signer := chunk["signer"].(string)
if contains(validators, signer) {
validChunks[signer] = chunk
}
}
return validChunks
}
// hypothesesTimes ํจ์๋ ๊ฐ์ค์ ํ์์คํฌํ๋ฅผ ๋ฐํํฉ๋๋ค.
func hypothesesTimes(hypotheses []map[string]interface{}) []int {
times := make([]int, len(hypotheses))
for i, hypothesis := range hypotheses {
times[i] = hypothesis["s_timestamp"].(int)
}
return times
}
// getHypothesisKeys ํจ์๋ ๊ฐ์ค์ ํค๋ฅผ ๋ฐํํฉ๋๋ค.
func getHypothesisKeys(hypotheses map[string]map[string]interface{}) []string {
keys := make([]string, 0, len(hypotheses))
for key := range hypotheses {
keys = append(keys, key)
}
return keys
}
// sealHypotheses ํจ์๋ ๊ฐ์ค์ ๋ด์ธํฉ๋๋ค.
func sealHypotheses(hypotheses []map[string]interface{}) []map[string]interface{} {
sealed := make([]map[string]interface{}, len(hypotheses))
for i, hypothesis := range hypotheses {
h := model.NewHypothesis(hypothesis)
sealed[i] = h.Seal()
}
return sealed
}
// countVotes ํจ์๋ ํฌํ ์๋ฅผ ์ง๊ณํฉ๋๋ค.
func countVotes(validators, datas []string) int {
votes := 0
for _, validator := range validators {
if contains(datas, validator) {
votes++
}
}
return votes
}
// main ํจ์๋ ChainMaker์ ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ๋ฉ์ธ ์์
์ ์ํํฉ๋๋ค.
func main() {
cm := NewChainMaker()
cm.Main()
}
ํจํค์ง ์ ์ธ ๋ฐ ์ํฌํธ
ChainMaker ๊ตฌ์กฐ์ฒด์ ์์ฑ์ ํจ์
Main ํจ์
Init ํจ์
Operation ํจ์
Refresh ํจ์
Sync ํจ์
Hosts ํจ์
SyncHosts ํจ์
Pull ํจ์
Append ํจ์
Consensus ํจ์
๋ถ๊ฐ ํจ์๋ค
main ํจ์
Last updated