Geth p2p/discover/database.go 코드 분석 (1)

  • by


소스 코드 소스:

https://github.com/NAKsir-melody/go-ethereum-korean

주의사항: 현재의 Geth 코드는 2023년 기준의 갱신이 많아졌으므로, 상기 자료는 4년전의 자료입니다.


그 내용을 참고하십시오.


목적:

Geth 한글 주석 프로젝트를 통해 소스 코드에서 각 함수, 변수, 구조체의 역할을 정리하고 소스 코드를 정리하기 위해 게시했습니다.

전체적으로 database.go 는 node 에 관한 정보를 db 화해 접속한 node 의 정보를 node db 에 넣거나 삭제하는 node 의 정보를 node db 로부터 삭제하는 등의 작업을 실시해, 각각 접속된 노드의 시간 등을 업데이트하고 관리하는 코드로 이해했습니다.

변수

다음 요소의 변수가 설정됩니다.

전체적으로 연결된 노드를 업데이트하고 관리하기 위한 변수임을 알 수 있습니다.

null 요소로 사용할 노드 ID,

노드 DB의 유효 시간,

유효 시간 이상으로 실행되는 시간 단위

var (
	nodeDBNilNodeID      = NodeID{}       // Special node ID to use as a nil element.
	// nil원소로서 사용할 특수한 노드 ID
	nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
	// 특정시간동안 보이지 않는 노드는 드롭되어야 한다
	nodeDBCleanupCycle   = time.Hour      // Time period for running the expiration task.
	// 초과 업무를 실행할 시간단위
)

구조체

노드 DB는 geth가 아는 모든 노드를 저장하는 데이터베이스 역할을 하는 구조 구조입니다.

// nodeDB stores all nodes we know about.
// nodeDB는 우리가 아는 모든 노드를 저장합니다.

type nodeDB struct {
	lvl    *leveldb.DB   // Interface to the database itself
	// db 자체 인터페이스
	self   NodeID        // Own node id to prevent adding it into the database
	// DB에 추가하는 것을 막기위한 자기의 node ID
	runner sync.Once     // Ensures we can start at most one expirer
	// 하나가 끝났을때만 실행가능한것을 보장함
	quit   chan struct{} // Channel to signal the expiring thread to stop
	// 스레드를 종료시키기 위한 시그널을 전송할 채널
}

기능

조건문을보고 path가 비어 있으면 newMemoryDB 함수를 실행합니다.

return값으로서는 newPersistentNodeDB함수를 return하고 있다.

// newNodeDB creates a new node database for storing and retrieving infos about
// known peers in the network. If no path is given, an in-memory, temporary
// database is constructed.
// newNodeDB 함수는 네트워크에서 알려진 노드에 대한 정보를 저장하고 반환합니다.

// 새 노드 db를 만듭니다.

경로가 주어지지 않으면 메모리에 임시 DB가 생성됩니다.

func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
	if path == "" {
		return newMemoryNodeDB(self)
	}
	return newPersistentNodeDB(path, version, self)
}

db 변수에 leveldb 함수를 사용하여 스토리지에 저장 한 후

nodeDB 구조체에 데이터를 반환하는 것 같습니다.

// newMemoryNodeDB creates a new in-memory node database without a persistent
// backend.
// newMemoryNodeDB 함수는 항상성을 가진 백앤드 없이 메모리에 노드 DB를 생성합니다.

func newMemoryNodeDB(self NodeID) (*nodeDB, error) {
	db, err := leveldb.Open(storage.NewMemStorage(), nil)
	if err !
= nil { return nil, err } return &nodeDB{ lvl: db, self: self, quit: make(chan struct{}), }, nil }

// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
// also flushing its contents in case of a version mismatch.
// newPersistenNodeDB 는, 항상성을 가지는 node DB 로서 leveldb 를 생성 또는 오픈합니다.

// 버전이 맞지 않을 때 모두 플래시 저장
//Go 언어에서 flush 기능은 주로 버퍼를 비우는 것을 의미합니다.

이것은 데이터 쓰기 동작이 발생할 때 버퍼에 축적된 데이터를 즉시 파일 또는 네트워크에 기록하기 위해 버퍼를 비우는 것을 수행하는 것이다.

//Go 언어로 flush 기능을 수행하는 가장 일반적인 방법은 io.Writer 인터페이스를 구현하는 객체의 Flush() 메서드를 호출하는 것입니다.

예를 들어, 파일을 쓰는 경우,
// 파일에 데이터를 기록하기 위해 버퍼된 데이터를 즉시 파일에 기록하기 위해 Flush() 메소드를 호출할 수 있습니다.

func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
	opts := &opt.Options{OpenFilesCacheCapacity: 5}
	db, err := leveldb.OpenFile(path, opts)
	if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
		db, err = leveldb.RecoverFile(path, nil)
	}
	if err !
= nil { return nil, err } // The nodes contained in the cache correspond to a certain protocol version. // Flush all nodes if the version doesn't match. // 노드들은 특정 프로토콜 버전과 연관된 캐시에 적재된다 // 버전이 맞지 않을 경우 모든 노드를 플러시한다 currentVer := make(()byte, binary.MaxVarintLen64) currentVer = currentVer(:binary.PutVarint(currentVer, int64(version))) blob, err := db.Get(nodeDBVersionKey, nil) switch err { case leveldb.ErrNotFound: // Version not found (i.e. empty cache), insert it // 버전이 찾아지지 않음. 삽입 if err := db.Put(nodeDBVersionKey, currentVer, nil); err !
= nil { db.Close() return nil, err } case nil: // Version present, flush if different // 버전이 존재. 다를경우 플러시 if !
bytes.Equal(blob, currentVer) { db.Close() if err = os.RemoveAll(path); err !
= nil { return nil, err } return newPersistentNodeDB(path, version, self) } } return &nodeDB{ lvl: db, self: self, quit: make(chan struct{}), }, nil }

// makeKey generates the leveldb key-blob from a node id and its particular
// field of interest.
// madeKey 함수는 노드 ID와 특정 관심 필드에서 level db의 keyblob을 생성합니다.

func makeKey(id NodeID, field string) ()byte {
	if bytes.Equal(id(:), nodeDBNilNodeID(:)) {
		return ()byte(field)
	}
	return append(nodeDBItemPrefix, append(id(:), field...)...)
}

// splitKey tries to split a database key into a node id and a field part.
// splitkey 함수는 db key 를 node id 와 필드 파트를 구별하려고 한다
func splitKey(key ()byte) (id NodeID, field string) {
	// If the key is not of a node, return it plainly
	// 키가 노드가 아닐경우 순수하게 리턴한다
	if !
bytes.HasPrefix(key, nodeDBItemPrefix) { return NodeID{}, string(key) } // Otherwise split the id and field // 아니라면 id와 필드를 분리한다 item := key(len(nodeDBItemPrefix):) copy(id(:), item(:len(id))) field = string(item(len(id):)) return id, field }

// fetchInt64 retrieves an integer instance associated with a particular
// 데이터베이스 키.
// fetchInt64는 특정 db 키와 연관된 정수 인스턴스를 반환합니다.

func (db *nodeDB) fetchInt64(key ()byte) int64 {
	blob, err := db.lvl.Get(key, nil)
	if err !
= nil { return 0 } val, read := binary.Varint(blob) if read <= 0 { return 0 } return val }

// storeInt64 update a specific database entry to the current time instance as a
// unix timestamp.
// storeInt64는 DB의 특정 항목을 현재 Unix 타임스탬프 인스턴스로 업데이트합니다.

func (db *nodeDB) storeInt64(key ()byte, n int64) error {
	blob := make(()byte, binary.MaxVarintLen64)
	blob = blob(:binary.PutVarint(blob, n))

	return db.lvl.Put(key, blob, nil)
}

// node retrieves a node with a given id from the database.
// Node 함수는 주어진 id의 노드를 db에서 반환합니다.

func (db *nodeDB) node(id NodeID) *Node {
	blob, err := db.lvl.Get(makeKey(id, nodeDBDiscoverRoot), nil)
	if err !
= nil { return nil } node := new(Node) if err := rlp.DecodeBytes(blob, node); err !
= nil { log.Error("Failed to decode node RLP", "err", err) return nil } node.sha = crypto.Keccak256Hash(node.ID(:)) return node }

DB의 UPDATE 쿼리와 비슷한 것 같습니다.

// updateNode inserts – potentially overwriting – a node into the peer database.
// updateNode는 노드를 피어 db에 삽입하거나 덮어씁니다.

func (db *nodeDB) updateNode(node *Node) error {
	blob, err := rlp.EncodeToBytes(node)
	if err !
= nil { return err } return db.lvl.Put(makeKey(node.ID, nodeDBDiscoverRoot), blob, nil) }

DB의 Delete 쿼리와 비슷한 것 같습니다.

// deleteNode deletes all information/keys associated with a node.
// deleteNode는 노드와 관련된 모든 정보와 키를 삭제합니다.

func (db *nodeDB) deleteNode(id NodeID) error {
	deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil)
	for deleter.Next() {
		if err := db.lvl.Delete(deleter.Key(), nil); err !
= nil { return err } } return nil }

// ensureExpirer is a small helper method ensuring that the data expiration
// mechanism is running. If the expiration goroutine is already running, this
// method simply returns.
//
// The goal is to start the data evacuation only after the network successfully
// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
// it would require significant overhead to exactly trace the first successful
// convergence, it’s simpler to “ensure” the correct state when an appropriate
// condition occurs (ie a successful bonding), and discard further events.
// ensureExpirerer는 도우미 함수에서 데이터 만료 메커니즘이 작동하는지 확인합니다.

// 만료된 골틴이 이미 실행 중이면 단순히 반환
// 네트워크에 성공적으로 연결할 때 데이터 철수를 시작하는 데 사용됩니다.

// (좋은 시드 노드를 덤프하는 것을 방지하기 위해)
// 첫 번째 성공적인 수렴을 정확하게 추출하려면 많은 오버헤드가 필요합니다.

// 특정 상태가 발생했을 때 상태가 올바른지 확인하는 것이 간단하며 더 이상 이벤트를 버립니다.

func (db *nodeDB) ensureExpirer() {
	db.runner.Do(func() { go db.expirer() })
}

// expirer should be started in a go routine, and is responsible for looping ad
// infinitum and dropping stale data from the database.
// expirer는 골틴에서 실행해야 하며 무한 루프와 db에서 데이터를 지울 책임이 있습니다.

func (db *nodeDB) expirer() {
	tick := time.NewTicker(nodeDBCleanupCycle)
	defer tick.Stop()
	for {
		select {
		case <-tick.C:
			if err := db.expireNodes(); err !
= nil { log.Error("Failed to expire nodedb items", "err", err) } case <-db.quit: return } } }

// expireNodes iterates over the database and deletes all nodes that have not
// been seen (ie received a pong from) for some allotted time.
// expirenodes는 dB를 반복하여 특정 슬랫 시간 동안 보이지 않는 모든 노드를 삭제합니다.

func (db *nodeDB) expireNodes() error {
	threshold := time.Now().Add(-nodeDBNodeExpiration)

	// Find discovered nodes that are older than the allowance
	// 허용치보다 오래된 발견된 노드를 찾는다
	it := db.lvl.NewIterator(nil, nil)
	defer it.Release()

	for it.Next() {
		// Skip the item if not a discovery node
		// 디스커버리 노드가 아니면 스킵
		id, field := splitKey(it.Key())
		if field !
= nodeDBDiscoverRoot { continue } // Skip the node if not expired yet (and not self) // 아직 시간초과 되지 않았다면 스킵 if !
bytes.Equal(id(:), db.self(:)) { if seen := db.bondTime(id); seen.After(threshold) { continue } } // Otherwise delete all associated information // 아니라면 관련된 모든정보를 삭제한다 db.deleteNode(id) } return nil }

// lastPing retrieves the time of the last ping packet send to a remote node,
// requesting binding.
// lastPing 함수는 마지막 ping 패킷이 원격 노드로 전송된 시간을 반환하고 바인딩을 요청합니다.

func (db *nodeDB) lastPing(id NodeID) time.Time {
	return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
}

// updateLastPing updates the last time we tried contacting a remote node.
// updateLastPing 함수는 원격 노드에 마지막으로 연결하려고 시도한 시간을 업데이트합니다.

func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error {
	return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
}

// bondTime retrieves the time of the last successful pong from remote node.
// bondTime 함수는 마지막 pong이 원격 노드에서 성공한 시간을 반환합니다.

func (db *nodeDB) bondTime(id NodeID) time.Time {
	return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
}

// hasBond reports whether the given node is considered bonded.
// hasBond 함수는 주어진 노드가 있다고 생각되는지 여부를 보고합니다.

func (db *nodeDB) hasBond(id NodeID) bool {
	return time.Since(db.bondTime(id)) < nodeDBNodeExpiration
}

// updateBondTime updates the last pong time of a node.
// updateBondTime 함수는 노드의 마지막 pong 시간을 업데이트합니다.

func (db *nodeDB) updateBondTime(id NodeID, instance time.Time) error {
	return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
}

// findFails retrieves the number of findnode failures since bonding.
// findFails 함수는 본딩 후 findnode 실패 횟수를 반환합니다.

func (db *nodeDB) findFails(id NodeID) int {
	return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
}

// updateFindFails updates the number of findnode failures since bonding.
// updateFindFails 함수는 본딩 이후의 find node 실패 수를 업데이트합니다.

func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
	return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
}

// querySeeds retrieves random nodes to be used as potential seed nodes
// for bootstrapping.
// querySeeds 함수는 부트스트랩에 대한 시드 노드로 가능한 랜덤 노드를 반환합니다.

func (db *nodeDB) querySeeds(n int, maxAge time.Duration) ()*Node {
	var (
		now   = time.Now()
		nodes = make(()*Node, 0, n)
		it    = db.lvl.NewIterator(nil, nil)
		id    NodeID
	)
	defer it.Release()

seek:
	for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
		// Seek to a random entry. The first byte is incremented by a
		// random amount each time in order to increase the likelihood
		// of hitting all existing nodes in very small databases.
		// 랜덤 엔트리를 위한 검색
		// 첫바이트는 작은 db에 존재하는 모든 노드를 히트할 확률을 높이기 위해
		// 매시간 랜덤량으로 증가된다
		ctr := id(0)
		rand.Read(id(:))
		id(0) = ctr + id(0)%16
		it.Seek(makeKey(id, nodeDBDiscoverRoot))

		n := nextNode(it)
		if n == nil {
			id(0) = 0
			continue seek // iterator exhausted
			// 반복자 종료
		}
		if n.ID == db.self {
			continue seek
		}
		if now.Sub(db.bondTime(n.ID)) > maxAge {
			continue seek
		}
		for i := range nodes {
			if nodes(i).ID == n.ID {
				continue seek // duplicate
				// 중복
			}
		}
		nodes = append(nodes, n)
	}
	return nodes
}

// reads the next node record from the iterator, skipping over other
// database entries.
// 이터레이터에서 다음 노드 기록을 읽고 다른 db 항목은 건너뜁니다.

func nextNode(it iterator.Iterator) *Node {
	for end := false; !
end; end = !
it.Next() { id, field := splitKey(it.Key()) if field !
= nodeDBDiscoverRoot { continue } var n Node if err := rlp.DecodeBytes(it.Value(), &n); err !
= nil { log.Warn("Failed to decode node RLP", "id", id, "err", err) continue } return &n } return nil }

// close flushes and closes the database files.
// close 함수는 db 파일을 flush하고 닫습니다.

func (db *nodeDB) close() {
	close(db.quit)
	db.lvl.Close()
}