소스 코드 소스:
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가 아는 모든 노드를 저장하는 데이터베이스 역할을 하는 구조 구조입니다.
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하고 있다.
경로가 주어지지 않으면 메모리에 임시 DB가 생성됩니다.
func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
if path == "" {
return newMemoryNodeDB(self)
}
return newPersistentNodeDB(path, version, self)
}
db 변수에 leveldb 함수를 사용하여 스토리지에 저장 한 후
nodeDB 구조체에 데이터를 반환하는 것 같습니다.
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
}
이것은 데이터 쓰기 동작이 발생할 때 버퍼에 축적된 데이터를 즉시 파일 또는 네트워크에 기록하기 위해 버퍼를 비우는 것을 수행하는 것이다.
예를 들어, 파일을 쓰는 경우,
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
}
func makeKey(id NodeID, field string) ()byte {
if bytes.Equal(id(:), nodeDBNilNodeID(:)) {
return ()byte(field)
}
return append(nodeDBItemPrefix, append(id(:), field...)...)
}
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
}
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
}
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)
}
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 쿼리와 비슷한 것 같습니다.
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 쿼리와 비슷한 것 같습니다.
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
}
func (db *nodeDB) ensureExpirer() {
db.runner.Do(func() { go db.expirer() })
}
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
}
}
}
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
}
func (db *nodeDB) lastPing(id NodeID) time.Time {
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
}
func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
}
func (db *nodeDB) bondTime(id NodeID) time.Time {
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
}
func (db *nodeDB) hasBond(id NodeID) bool {
return time.Since(db.bondTime(id)) < nodeDBNodeExpiration
}
func (db *nodeDB) updateBondTime(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
}
func (db *nodeDB) findFails(id NodeID) int {
return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
}
func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
}
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
}
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
}
func (db *nodeDB) close() {
close(db.quit)
db.lvl.Close()
}