Ich habe das gelbe Papier viele Male sowie verschiedene Artikel gelesen, ich denke, wenn ich die Antwort hier nicht finden kann, werde ich in den Code schauen.
Mein Verständnis ist, dass jedes Vertragskonto einen Speicherstamm enthält. und Sie können die Wurzel aus der Level-Datenbank abrufen.
Aber was ist der Wert in der leveldb?
Hier sind einige meiner Fragen:
LevelDB ist ein Schlüsselwert. Wenn wir also den Speicherstamm verwenden können, um einen Vertrag zu speichern, was ist dann der Wert?
Ich werde eine wilde Vermutung annehmen, dass der Wert der Patrica-Baum ist?
Wenn 2 wahr ist, ist es dann nicht jedes Mal extrem ineffizient, wenn wir den Vertrag ausführen wollen – wir müssen die Speicherung des gesamten Vertrages übernehmen …? Wenn ein Vertrag viel Speicherplatz hat, ist dies möglicherweise extrem langsam?
Letzte Frage – Mein Verständnis, dass alle ERC20-Token-Guthabeninformationen im Speicher gespeichert sind, bedeutet dies, wenn ich ICO auf 5 Millionen Konten mache – bedeutet dies, dass ich jetzt einen riesigen Speicher habe, der extrem langsam abzurufen ist? - Vorausgesetzt, dass der gesamte Speicher des Vertrags auf einmal abgerufen wird.
Anweisungen zum Speichern und Abrufen von Daten sind:
SLOAD: {
execute: opSload,
gasCost: gasSLoad,
validateStack: makeStackFunc(1, 1),
valid: true,
},
SSTORE: {
execute: opSstore,
gasCost: gasSStore,
validateStack: makeStackFunc(2, 0),
valid: true,
writes: true,
},
Und so werden sie codiert:
func opSload(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
loc := stack.peek()
val := evm.StateDB.GetState(contract.Address(), common.BigToHash(loc))
loc.SetBytes(val.Bytes())
return nil, nil
}
func opSstore(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
loc := common.BigToHash(stack.pop())
val := stack.pop()
evm.StateDB.SetState(contract.Address(), loc, common.BigToHash(val))
evm.interpreter.intPool.put(val)
return nil, nil
}
SetState und GetState werden wie folgt implementiert:
func (self *StateDB) GetState(addr common.Address, bhash common.Hash) common.Hash {
stateObject := self.getStateObject(addr)
if stateObject != nil {
return stateObject.GetState(self.db, bhash)
}
return common.Hash{}
}
func (self *StateDB) SetState(addr common.Address, key, value common.Hash) {
stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetState(self.db, key, value)
}
}
// SetState updates a value in account storage.
func (self *stateObject) SetState(db Database, key, value common.Hash) {
self.db.journal.append(storageChange{
account: &self.address,
key: key,
prevalue: self.GetState(db, key),
})
self.setState(key, value)
}
// GetState returns a value in account storage.
func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
value, exists := self.cachedStorage[key]
if exists {
return value
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(key[:])
if err != nil {
self.setError(err)
return common.Hash{}
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
}
value.SetBytes(content)
}
self.cachedStorage[key] = value
return value
}
Merkle Patricia Trie speichert diese Daten:
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
Wie Sie sehen können, enthält das Root common.Hash
Mitglied der Struktur den Hash des internen Speichers des Vertrags. Das bedeutet, wo immer Sie den Speicher des Vertrages aktualisieren, wird sich der Hash ändern, und da das Account
Objekt Teil des gesamten Tries ist, wird die Änderung an höhere Knoten weitergegeben und der StateRoot
des Blocks wird sich am Ende ändern.
Also kurz:
Root common.hash
ist nur der Unterpunkt der vertragsgemäßen Speicherunggeth
hat einen Cache und kann je nach Konfiguration so groß sein, dass er ausreicht, um viele Schlüssel-Wert-Paare zu speichern, ohne dass etwas gelesen werden muss. Außerdem akkumuliert LevelDB Schreibvorgänge, bis 128 MB Schreibvorgänge akkumuliert sind, wodurch IO reduziert wirdGetState()
Funktion verwendet Schlüssel (eine Art von common.Hash), um den Wert zu suchen, es ist eine einzelne Operation. Warum sollte es den gesamten Trie lesen müssen? Die kostspielige Operation hier ist der commit
gesamte Trie auf die Festplatte, ein Prozess, bei dem alle übergeordneten Knoten (die sich höher im Trie befinden) mit einem neuen Hash aktualisiert werden.go-ethereum
in gemeinsam genutzte Bibliotheken, um sie in meiner C++ Ethereum-Brieftasche zu verwenden, anstatt nativen C++-Code zu verwenden.
Tjaden Hess
Benutzer2584960