관리 메뉴

멍개의 연구소

[블록체인] 블록체인을 위한 고랭(golang) 본문

블록체인

[블록체인] 블록체인을 위한 고랭(golang)

멍개. 2022. 8. 27. 15:37

go는 분산환경에서 특화된 프로그래밍 언어입니다. 블록체인 기반으로 된 대부분이 go언어로 작성되어 있습니다. 

필자는 hyperledger fabric에서 체인코드를 만들기 위해 go를 공부합니다.

먼저 프로그래밍의 가장 기본인 조건문과 반복문

· 조건문

package main

import "fmt"

func main() {
    var i int = 11;

    if i % 2 == 0 {
      fmt.Println("짝수")
      fmt.Println(i)
    } else {
      fmt.Println("홀수")
      fmt.Println(i)
    }
}

go 자체적으로 print함수가 있지만 별로 이쁘지 않아서 fmt를 이용하여 출력합니다. 일단 연산자는 C와 동일하게 사용됩니다. &&, ||  등. go에서도 포인터 개념이 등장합니다.

· 반복문

package main

import "fmt"

func main() {
  myMap := map[string]string{
    "A": "apple",
    "B": "Banana",
    "C": "Charlie",
  }
  for key, val := range myMap{
    fmt.Println(key, val)
  }

  myArray := [4]int{1,2,3,4}
  for key, val := range myArray{
    fmt.Println(key, val)
  }
}

go에서도 map이라고 하는 자료형과 array 자료형을 제공합니다. map은 json이나 dict과 같은 자료형으로 이해하면 됩니다. python처럼 range를 이용하여 루프를 돌릴 수 있습니다. map에서는 key, array에서는 index를 출력할 수 있습니다.

· 자료형

go에서 다섯 종류의 타입을 가지고 있습니다. 불리언, 문자열, 정수형, 실수형, 기타타입이 있습니다.

bool
string
int
float
byte
rune
[]
package main

func main(){
  var i int = 100
  var j uint = uint(i)
  var f float32 = float32(i)
  println(f, j)

  str := "AAA"
  bytes := []byte(str)
  str2 := string(bytes)
  println(bytes, str2)
}
package main

import "fmt"

func main() {
  var a [3]int

  a[0] = 0
  a[1] = 10
  a[2] = 20

  fmt.Println(a)
  fmt.Println(a[1])
  fmt.Println(a[2])

  var b []int
  b = []int{1,2,3,4,5}
  fmt.Println(b)
  fmt.Println(len(b), cap(b))
}

go에서는 변수를 선언하는 방법이 두 가지 입니다.

 
var [변수이름] [타입] = 값;
[변수이름] := 값;

var 대신 const키워드를 사용하면 변수가 아니라 상수로 사용할 수 있습니다.

package main

import "fmt"

func main() {
   a := map[string]string{
   }

   a["test"] = "10"
   a["t"] = "123"
   fmt.Println(a)
}

map 자료형은 key-value의 타입을 정해줘야 합니다.

· 구조체

 
package main

import "fmt"

type Person struct{
  name string
  age int
}

func main() {
  p1 := Person{}

  p1.name = "pjt"
  p1.age = 26

  fmt.Println(p1)
  fmt.Println(p1.name)
  fmt.Println(p1.age)

  p2 := new(Person)

  p2.name = "contructor pjt"
  p2.age = 26
  fmt.Println(p2)

}

 

구조체의 사용법은 C와 거의유사합니다. new를 이용하여 객체의 형태로 생성해도 ->가 아닌 .로 참조합니다.

· method

package main

import "fmt"

type Rect struct {
  width, height int
}

func (r Rect) areaValue() int{
  return r.width * r.height
}

func (r *Rect) areaPointer() int{
  return r.width * r.height
}

func main() {
  rect1 := Rect{10, 20}
  area := rect1.areaValue()

  fmt.Println(area)

  rect2 := new(Rect)
  rect2.width = 11
  rect2.height = 20
  area2 := rect2.areaPointer()
  fmt.Println(area2)
}

메소드를 만들 땐 func와 함수이름 사이에 receiver라고 하는 것을 넣어줍니다. receiver를 만들땐 value 형태와 pointer 형태로 만들수 있습니다. 이 둘의 차이는 call-by-value와 call-by-reference의 차이입니다.

· 인터페이스

package main

import "fmt"

type Rect struct {
	width, height int
}

type Shape interface {
	area() int
}

func (r *Rect) area() int {
	return r.width * r.height
}

func totalArea(shapes ...Shape) int {
    var area int
    for _, s := range shapes {
        area += s.area()
    }
    return area
}

func main() {
	a := new(Rect)
	a.width = 10
	a.height = 20

	b := new(Rect)
	b.width = 100
	b.height = 200

	fmt.Println(totalArea(a, b))
}

struct는 속성값을 가지고 있는 것이라면 interface는 메소드를 가지고 있습니다. 구조체에 억지로 인터페이스를 만들려고 한게 느껴지는 부분입니다.

· 예외처리

package main

import (
	"fmt"
	"os"
	//"log"
)

type error interface {
	Error() string
}

func main(){

	ff, err := os.Open("./test.txt")

	switch err.(type) {
	default: // no error
  	fmt.Println(12345)
	case error:
		fmt.Println(123)
  	log.Fatal(err.Error())
	}
	fmt.Println(ff)
}

go에서는 try~ catch, except를 제공하지 않고 에러를 반환하는 형태로 코드를 작성합니다.

· defer/panic

 
package main

import "fmt"

func test() {
	fmt.Println("test")
}

func main() {
	defer test()
	fmt.Println(1)
	fmt.Println(2)
	// panic(1)
	fmt.Println(3)
}​
# 실행결과
1
2
3
test​

defer는 해당 코드를 바로 시작하지 않고 함수 호출이 끝났을 때 호출됩니다. 만약 panic 주석을 풀면 다음과 같이 결과를 보여줍니다.

1
2
test
panic: 1

goroutine 1 [running]:
main.main()
	/Users/pjt/Desktop/go/src/defer.go:13 +0xdd
exit status 2

· 고루틴

고에서는 고루틴을 이용하면 매우 편하게 비동기적인 실행을 할 수 있습니다.

package main

import (
	"fmt"
	"time"
	"sync"
	"runtime"
)

func say(s string){
	for i := 0 ; i < 100 ; i ++ {
		fmt.Println(s, "***", i)
	}
}

//고루틴
func main() {
	go say("test0")
	go say("test1")
	go say("test2")
	go say("test3")
	time.Sleep(time.Second *3)
}
package main

import (
	"fmt"
	"sync"
	"runtime"
)
func say(s string){
	for i := 0 ; i < 100 ; i ++ {
		fmt.Println(s, "***", i)
	}
}

// 익명함수 고루틴
func main() {
	runtime.GOMAXPROCS(4) // 4개의 CPU를 이용하여 고루틴 처리, go는 기본적으로 CPU 1개를 사용하여 처리
	var wait sync.WaitGroup
	wait.Add(2)

	go func () {
		defer wait.Done()
		fmt.Println("hello")
	} ()

	go func () {
		defer wait.Done()
		fmt.Println("world")
	}()

	wait.Wait()
}

고루틴은 비동기이기 때문에 코드실행이 완료되기 전에 함수가 끝나버리면 그대로 종료됩니다. 하지만 sync.WaitGroup를 이용하면 비동기로 호출되는 것들을 기다릴 수 있습니다.

· 채널

package main

import "fmt"

func main() {
	ch := make(chan int, 10)

	go func() {
		fmt.Println("goroutine")
		ch <- 123
	}()

	fmt.Println(<-ch)
}

make를 이용하여 ch 변수에 int형 데이터를 받겠다고 채널을 만들어 줍니다. 10은 버퍼크기 입니다.

ch <- 값은 ch로 값을 보내는 것을 의미합니다. <- ch는 ch 채널의 값을 가져옵니다.

package main

import "fmt"

func main() {
	ch := make(chan int, 10)

	ch <- 11
	ch <- 12
	ch <- 13
	ch <- 14
	ch <- 15
	ch <- 16
	ch <- 17
	ch <- 18
	ch <- 19
	ch <- 20
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

여기서 10개의 값을 채널로 보내게 됩니다. 만약 make(chan int, 10보다 작은수)로 채널을 생성하면 에러가 발생합니다.

$ go run channel.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/Users/pjt/Desktop/go/src/channel.go:13 +0xe9
exit status 2

빠르게 go를 알아보았습니다.

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * The sample smart contract for documentation topic:
 * Writing Your First Blockchain Application
 */

package main

/* Imports
 * 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
 * 2 specific Hyperledger Fabric specific libraries for Smart Contracts
 */
import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
)

// Define the Smart Contract structure
type SmartContract struct {
}

// Define the car structure, with 4 properties.  Structure tags are used by encoding/json library
type Car struct {
	Make   string `json:"make"`
	Model  string `json:"model"`
	Colour string `json:"colour"`
	Owner  string `json:"owner"`
}

/*
 * The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network
 * Best practice is to have any Ledger initialization in separate function -- see initLedger()
 */
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}

/*
 * The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
 * The calling application program has also specified the particular smart contract function to be called, with arguments
 */
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

	// Retrieve the requested Smart Contract function and arguments
	function, args := APIstub.GetFunctionAndParameters()
	// Route to the appropriate handler function to interact with the ledger appropriately
	if function == "queryCar" {
		return s.queryCar(APIstub, args)
	} else if function == "initLedger" {
		return s.initLedger(APIstub)
	}else if function == "createCar" {
		return s.createCar(APIstub, args)
	}else if function == "testCreate" {
		return s.testCreate(APIstub, args)
	}else if function == "queryAllCars" {
		return s.queryAllCars(APIstub)
	} else if function == "changeCarOwner" {
		return s.changeCarOwner(APIstub, args)
	}

	return shim.Error("Invalid Smart Contract function name.")
}

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	return shim.Success(carAsBytes)
}

func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
	cars := []Car{
		Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
		Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
		Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
		Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
		Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
		Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
		Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
		Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
		Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
		Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
	}

	i := 0
	for i < len(cars) {
		fmt.Println("i is ", i)
		carAsBytes, _ := json.Marshal(cars[i])
		APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
		fmt.Println("Added", cars[i])
		i = i + 1
	}

	return shim.Success(nil)
}

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 5 {
		return shim.Error("Incorrect number of arguments. Expecting 5")
	}

	var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}

	carAsBytes, _ := json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

func (s *SmartContract) testCreate(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 5 {
		return shim.Error("Incorrect number of arguments. Expecting 5")
	}

	var cars.
func main() {

	// Create a new Smart Contract
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}

hyperledger fabric의 샘플 체인코드의 일부입니다. SmartContract를 포인터 형태로 인터페이스를 쭉 생성한 것을 알 수 있습니다. 그리고 shim.Start에 생성된 객체를 전달하면 실행 실행이 되는 구조입니다.

Comments