He leído el papel amarillo muchas veces, así como varios artículos, supongo que si no puedo encontrar la respuesta aquí, buscaré el código.
Tengo entendido que cada cuenta de contrato contiene una raíz de almacenamiento. y puede recuperar la raíz del nivel db.
Pero, ¿cuál es el valor en el leveldb?
Estas son algunas de mis preguntas:
LevelDB es clave-valor, por lo que si podemos usar la raíz de almacenamiento para obtener el almacenamiento de un contrato, ¿cuál es el valor?
Voy a hacer una suposición descabellada, ¿que el valor es el árbol patrica?
Si 2 es verdadero, ¿no es extremadamente ineficiente cada vez que queremos ejecutar el contrato? ¿Tenemos que tomar el almacenamiento de todo el contrato? Si un contrato tiene mucho almacenamiento, ¿entonces es potencialmente extremadamente lento hacerlo?
Última pregunta: tengo entendido que toda la información del saldo del token ERC20 se almacena en el almacenamiento, entonces esto significa que si hago ICO en 5 millones de cuentas, ¿esto significa que ahora tengo un gran almacenamiento que es extremadamente lento de recuperar? - Dado que todo el almacenamiento del contrato se recupera de una sola vez.
Las instrucciones que almacenan y recuperan datos son:
SLOAD: {
execute: opSload,
gasCost: gasSLoad,
validateStack: makeStackFunc(1, 1),
valid: true,
},
SSTORE: {
execute: opSstore,
gasCost: gasSStore,
validateStack: makeStackFunc(2, 0),
valid: true,
writes: true,
},
Y así es como se codifican:
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 y GetState se implementan así:
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 almacena estos datos:
// 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
}
Como puede ver, el Root common.Hash
miembro de la estructura tiene el hash del almacenamiento interno del contrato. Esto significa que, donde sea que actualice el almacenamiento del contrato, el hash cambiará y, como el Account
objeto es parte de todo el proceso, el cambio se propagará a los nodos superiores y StateRoot
el bloque cambiará al final.
Entonces, en resumen:
Root common.hash
es solo el sub-trie del almacenamiento del contrato sologeth
tiene caché y, dependiendo de la configuración, puede ser tan grande que será suficiente para contener muchos pares clave-valor sin necesidad de leer nada. Además, LevelDB acumula escrituras hasta que se acumulan 128 MB de escrituras, reduciendo IOGetState()
La función usa la clave (un tipo de hash común) para buscar el valor, es una sola operación. ¿Por qué tendría que leer todo el trie? La operación costosa aquí es la commit
de todo el trie al disco, un proceso en el que todos los nodos principales (ubicados más arriba en el trie) se actualizan con un nuevo hash.go-ethereum
en bibliotecas compartidas para usarlas en mi billetera C++ Ethereum, en lugar de usar el código nativo de C++.
tjaden hess
usuario2584960