Jake Song(Onther Inc.)
Carl Park(Onther Inc.)
Genesis 블록에 Stamina 컨트랙트를 넣어두고 내부적으로 컨트랙트 함수를 호출하여 가스 비용을
위임하는 것을 목적으로 하였다. 이를 위해 EVM 실행 함수를 사용하여 컨트랙트 실행 함수를 만들었고
tx-execution, tx-pool, state-transition 단계에 적용하였다.
var StaminaContractAddressHex = "0x000000000000000000000000000000000000dead"
var StaminaContractAddress = common.HexToAddress(StaminaContractAddressHex)
// deployed bytecode
var StaminaContractBin = "0x6080604052600436106100d0576000f..."
type accountWrapper struct {
address common.Address
}
func (a accountWrapper) Address() common.Address {
return a.address
}
var BlockchainAccount = accountWrapper{common.HexToAddress("0x00")}
var StaminaAccount = accountWrapper{StaminaContractAddress}
var StaminaABI, _ = abi.JSON(strings.NewReader(contract.StaminaABI))
컨트랙트 함수를 불러오기 위해 자체적으로 EVM을 실행하는 것을 정의
func GetDelegatee(evm *vm.EVM, from common.Address) (common.Address, error) {
data, err := staminaCommon.StaminaABI.Pack("getDelegatee", from)
if err != nil {
return common.Address{}, err
}
ret, _, err := evm.StaticCall(staminaCommon.BlockchainAccount, staminaCommon.StaminaContractAddress, data, 1000000)
if err != nil {
return common.Address{}, err
}
return common.BytesToAddress(ret), nil
}
func GetStamina(evm *vm.EVM, delegatee common.Address) (*big.Int, error) {
data, err := staminaCommon.StaminaABI.Pack("getStamina", delegatee)
if err != nil {
return big.NewInt(0), err
}
ret, _, err := evm.StaticCall(staminaCommon.BlockchainAccount, staminaCommon.StaminaContractAddress, data, 1000000)
if err != nil {
return big.NewInt(0), err
}
stamina := new(big.Int)
stamina.SetBytes(ret)
return stamina, nil
}
func AddStamina(evm *vm.EVM, delegatee common.Address, gas *big.Int) error {
data, err := staminaCommon.StaminaABI.Pack("addStamina", delegatee, gas)
if err != nil {
return err
}
_, _, err = evm.Call(staminaCommon.BlockchainAccount, staminaCommon.StaminaContractAddress, data, 1000000, big.NewInt(0))
return err
}
func SubtractStamina(evm *vm.EVM, delegatee common.Address, gas *big.Int) error {
data, err := staminaCommon.StaminaABI.Pack("subtractStamina", delegatee, gas)
if err != nil {
return err
}
_, _, err = evm.Call(staminaCommon.BlockchainAccount, staminaCommon.StaminaContractAddress, data, 1000000, big.NewInt(0))
return err
}
// moscow - arbitrary msg & header & author
func (pool *TxPool) newStaticEVM() *vm.EVM {
msg := types.NewMessage(
staminaCommon.BlockchainAccount.Address(),
&staminaCommon.StaminaContractAddress,
0,
big.NewInt(0),
1000000,
big.NewInt(1e9),
nil,
false,
)
vmConfig := vm.Config{}
ctx := NewEVMContext(
msg,
&types.Header{
Number: big.NewInt(0),
Time: big.NewInt(0),
Difficulty: big.NewInt(0),
},
pool.chain,
&common.Address{},
)
return vm.NewEVM(
ctx,
pool.currentState,
pool.chainconfig,
vmConfig,
)
}
var err error
staminaBinBytes, err := hex.DecodeString(staminaCommon.StaminaContractBin[2:])
if err != nil {
panic(err)
}
staminaCommon.StaminaContractAddress: {
Code: staminaBinBytes,
Balance: big.NewInt(0),
},
func (st *StateTransition) buyDelegateeGas(delegatee common.Address) error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
balance, err := stamina.GetStamina(st.evm, delegatee)
if err != nil {
return err
}
if balance.Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err
}
st.gas += st.msg.Gas()
st.initialGas = st.msg.Gas()
stamina.SubtractStamina(st.evm, delegatee, mgval)
return nil
}
func (st *StateTransition) preDelegateeCheck(delegatee common.Address) error {
// Make sure this transaction's nonce is correct.
if st.msg.CheckNonce() {
nonce := st.state.GetNonce(st.msg.From())
if nonce < st.msg.Nonce() {
return ErrNonceTooHigh
} else if nonce > st.msg.Nonce() {
return ErrNonceTooLow
}
}
return st.buyDelegateeGas(delegatee)
}
// get delegatee
delegatee, _ := stamina.GetDelegatee(evm, msg.From())
log.Info("GetDelegatee", "from", msg.From(), "delegatee", delegatee)
// moscow - if has delegatee
if delegatee != common.HexToAddress("0x00") {
if err = st.preDelegateeCheck(delegatee); err != nil {
return
}
// Pay intrinsic gas
gas, err := IntrinsicGas(st.data, contractCreation, homestead)
if err != nil {
return nil, 0, false, err
}
if err = st.useGas(gas); err != nil {
return nil, 0, false, err
}
if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
if vmerr != nil {
log.Debug("VM returned with error", "err", vmerr)
// The only possible consensus-error would be if there wasn't
// sufficient balance to make the transfer happen. The first
// balance transfer may never fail.
if vmerr == vm.ErrInsufficientBalance {
return nil, 0, false, vmerr
}
}
st.refundDelegateeGas(delegatee)
// TODO: gas fee to miner
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err
}
// moscow - original version
if err = st.preCheck(); err != nil {
return
}
func (st *StateTransition) refundDelegateeGas(delegatee common.Address) {
// Apply refund counter, capped to half of the used gas.
refund := st.gasUsed() / 2
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
st.gas += refund
// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
stamina.AddStamina(st.evm, delegatee, remaining)
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gas)
}
evm := pool.newStaticEVM()
delegatee, err := stamina.GetDelegatee(evm, from)
if err != nil {
return ErrStaminaGetDelegatee
}
if delegatee != common.HexToAddress("0x00") {
mgval := new(big.Int).Mul(tx.GasPrice(), big.NewInt(int64(tx.Gas())))
// delegatee should have enough stemina
// cost == GP * GL
if stamina, _ := stamina.GetStamina(evm, delegatee); stamina.Cmp(mgval) < 0 {
return ErrInsufficientStamina
}
// sender should have enough value
if pool.currentState.GetBalance(from).Cmp(tx.Value()) < 0 {
return ErrInsufficientValue
}
} else {
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
}
// Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
// moscow - do not drop tx if delegatee has enough stamina
evm := pool.newStaticEVM()
delegatee, _ := stamina.GetDelegatee(evm, addr)
stamina, _ := stamina.GetStamina(evm, delegatee)
balance := pool.currentState.GetBalance(addr)
var costlimit *big.Int
if stamina.Cmp(balance) >= 0 {
costlimit = stamina
} else {
costlimit = balance
}
drops, _ := list.Filter(costlimit, pool.currentMaxGas)
py-evm은 Ethereum의 하드포크에 따른 EVM을 정의하고 각 EVM을 상속하여 client를 구현하고 있다.
Frontier:
Homestead:
Tangerine Whistle:
Suprious Dragon:
Byzantium:
Byzantium EVM을 상속하는 Moscow EVM을 정의하여 가스비 위임 모델을 구현하였다.
EVM 실행 함수를 정의하고 Stamina 컨트랙트 함수를 실행하기 위한 함수를 정의함
def execute_bytecode(state,
origin,
gas_price,
gas,
to,
sender,
value,
data,
code,
code_address=None,
):
"""
Execute raw bytecode in the context of the current state of
the virtual machine.
"""
if origin is None:
origin = sender
# Construct a message
message = Message(
gas=gas,
to=to,
sender=sender,
value=value,
data=data,
code=code,
code_address=code_address,
)
# Construction a tx context
transaction_context = state.get_transaction_context_class()(
gas_price=gas_price,
origin=origin,
)
# Execute it in the VM
return state.get_computation(message, transaction_context).apply_computation(
state,
message,
transaction_context,
)
def getDelegate(state, delegator) :
fdata = keccak("getDelegate(address)".encode())
fnsig = fdata[0:4]
adata = encode_single('address', delegator)
data = fnsig + adata
vm_state = state
code = vm_state.account_db.get_code(STAMINA)
# params: (state, origin, gas_price, gas, to, sender, value, data, code, code_address=None)
computation = execute_bytecode(state, None, 100, 100, STAMINA, BLOCKCHAIN, 0, data, code, None)
assert(computation.is_success)
addr = computation._memory._bytes
return addr
def getStamina(state, delegate) :
fdata = keccak("getStamina(address)".encode())
fnsig = fdata[0:4]
adata = encode_single('address', delegate)
data = fnsig + adata
vm_state = state
code = vm_state.account_db.get_code(STAMINA)
# params: (state, origin, gas_price, gas, to, sender, value, data, code, code_address=None)
computation = execute_bytecode(state, None, 100, 100, STAMINA, BLOCKCHAIN, 0, data, code, None)
assert(computation.is_success)
ret = computation.output
return ret
def addStamina(state, delegate, val) :
fdata = keccak("addStamina(address,uint256)".encode())
fnsig = fdata[0:4]
adata = encode_single('(address,uint256)', [delegate,val])
data = fnsig + adata
vm_state = state
code = vm_state.account_db.get_code(STAMINA)
# params: (state, origin, gas_price, gas, to, sender, value, data, code, code_address=None)
computation = execute_bytecode(state, None, 100, 100, STAMINA, BLOCKCHAIN, 0, data, code, None)
assert(computation.is_success)
def subtractStamina(state, delegate, val) :
fdata = keccak("subtractStamina(address,uint256)".encode())
fnsig = fdata[0:4]
adata = encode_single('(address,uint256)', [delegate,val])
data = fnsig + adata
vm_state = state
code = vm_state.account_db.get_code(STAMINA)
# params: (state, origin, gas_price, gas, to, sender, value, data, code, code_address=None)
computation = execute_bytecode(state, None, 100, 100, STAMINA, BLOCKCHAIN, 0, data, code, None)
assert(computation.is_success)
delegatee설정 -> deposit -> transfer 과정을 만들고 가스비가 위임되는 것을 시나리오 형태로 구현
ADDRESS_1_PRIVATE_KEY = keys.PrivateKey(
decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8')
)
ADDRESS_2_PRIVATE_KEY = keys.PrivateKey(
decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d0')
)
ADDRESS_3_PRIVATE_KEY = keys.PrivateKey(
decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d1')
)
ADDRESS_4_PRIVATE_KEY = keys.PrivateKey(
decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d2')
)
ADDRESS_1 = Address(ADDRESS_1_PRIVATE_KEY.public_key.to_canonical_address())
ADDRESS_2 = Address(ADDRESS_2_PRIVATE_KEY.public_key.to_canonical_address())
ADDRESS_3 = Address(ADDRESS_3_PRIVATE_KEY.public_key.to_canonical_address())
ADDRESS_4 = Address(ADDRESS_4_PRIVATE_KEY.public_key.to_canonical_address())
BLOCKCHAIN = Address(b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x0a')
STAMINA = Address(b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02')
GENESIS_STATE = {
ADDRESS_1: {
"balance" : 10**19,
"nonce" : 0,
"code" : b"",
"storage" : {}
},
ADDRESS_2: {
"balance" : 10**19,
"nonce" : 0,
"code" : b"",
"storage" : {}
},
ADDRESS_3: {
"balance" : 10**19,
"nonce" : 0,
"code" : b"",
"storage" : {}
},
ADDRESS_4: {
"balance" : 10**19,
"nonce" : 0,
"code" : b"",
"storage" : {}
},
STAMINA: {
"balance" : 10**19,
"nonce" : 0,
"code" :
b'0x6080604052600436106100fc5...',
"storage" : {}
}
}
klass = chains.base.MiningChain.configure(
__name__='TestChain',
vm_configuration=(
(constants.GENESIS_BLOCK_NUMBER, ByzantiumVM),
))
chain = klass.from_genesis(MemoryDB(), GENESIS_PARAMS, GENESIS_STATE)
######### TX1 ###########################
# init
vm = chain.get_vm()
fdata = keccak("init(uint256)".encode())
fnsig = fdata[0:4]
adata = encode_single('uint256', 1000000000000000000)
data = fnsig + adata
nonce = vm.state.account_db.get_nonce(ADDRESS_1)
tx1 = vm.create_unsigned_transaction(
nonce=nonce,
gas_price=100,
gas=100000,
to=STAMINA,
value=0,
data=data,
)
signed_tx1 = tx1.as_signed_transaction(ADDRESS_1_PRIVATE_KEY)
new_header, receipt, computation = chain.apply_transaction(signed_tx1)
# We have to finalize the block first in order to be able read the
# attributes that are important for the PoW algorithm
block = chain.get_vm().finalize_block(chain.get_block())
# based on mining_hash, block number and difficulty we can perform
# the actual Proof of Work (PoW) mechanism to mine the correct
# nonce and mix_hash for this block
nonce, mix_hash = mine_pow_nonce(
block.number,
block.header.mining_hash,
block.header.difficulty)
block = chain.mine_block(mix_hash=mix_hash, nonce=nonce)
vm = chain.get_vm()
print("########### init (from: ADDRESS_1) ###############")
print("BLOCK1 ADDRESS_1 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_1)))
print("BLOCK1 ADDRESS_2 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_2)))
print("BLOCK1 ADDRESS_3 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_3)))
print("BLOCK1 ADDRESS_4 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_4)))
print("-----------------------")
############ TX2 ##################
# setDelegatee
vm = chain.get_vm()
fdata = keccak("setDelegatee(address)".encode())
fnsig = fdata[0:4]
adata = encode_single('address', ADDRESS_2)
data = fnsig + adata
nonce = vm.state.account_db.get_nonce(ADDRESS_3)
tx2 = vm.create_unsigned_transaction(
nonce=nonce,
gas_price=100,
gas=100000,
to=STAMINA,
value=0,
data=data,
)
signed_tx2 = tx2.as_signed_transaction(ADDRESS_3_PRIVATE_KEY)
_, _, computation = chain.apply_transaction(signed_tx2)
# We have to finalize the block first in order to be able read the
# attributes that are important for the PoW algorithm
block = chain.get_vm().finalize_block(chain.get_block())
# based on mining_hash, block number and difficulty we can perform
# the actual Proof of Work (PoW) mechanism to mine the correct
# nonce and mix_hash for this block
nonce, mix_hash = mine_pow_nonce(
block.number,
block.header.mining_hash,
block.header.difficulty)
block = chain.mine_block(mix_hash=mix_hash, nonce=nonce)
vm = chain.get_vm()
print("########### setDelegatee (delegate: ADDRESS_3, delegator: ADDRESS_2)###############")
print("BLOCK2 ADDRESS_1 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_1)))
print("BLOCK2 ADDRESS_2 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_2)))
print("BLOCK2 ADDRESS_3 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_3)))
print("BLOCK2 ADDRESS_4 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_4)))
print("-----------------------")
############ TX3 ##################
# deposit
vm = chain.get_vm()
fdata = keccak("deposit(address)".encode())
fnsig = fdata[0:4]
adata = encode_single('address', ADDRESS_3)
data = fnsig + adata
nonce = vm.state.account_db.get_nonce(ADDRESS_2)
tx3 = vm.create_unsigned_transaction(
nonce=nonce,
gas_price=100,
gas=100000,
to=STAMINA,
value=1000000000000000000,
data=data,
)
signed_tx3 = tx3.as_signed_transaction(ADDRESS_2_PRIVATE_KEY)
_, _, computation = chain.apply_transaction(signed_tx3)
# We have to finalize the block first in order to be able read the
# attributes that are important for the PoW algorithm
block = chain.get_vm().finalize_block(chain.get_block())
# based on mining_hash, block number and difficulty we can perform
# the actual Proof of Work (PoW) mechanism to mine the correct
# nonce and mix_hash for this block
nonce, mix_hash = mine_pow_nonce(
block.number,
block.header.mining_hash,
block.header.difficulty)
block = chain.mine_block(mix_hash=mix_hash, nonce=nonce)
vm = chain.get_vm()
print("########### deposit (delegate: ADDRESS_3, delegator: ADDRESS_2) ###############")
print("BLOCK3 ADDRESS_1 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_1)))
print("BLOCK3 ADDRESS_2 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_2)))
print("BLOCK3 ADDRESS_3 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_3)))
print("BLOCK3 ADDRESS_4 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_4)))
print("-----------------------")
############ TX4 ##################
# transfer
vm = chain.get_vm()
nonce = vm.state.account_db.get_nonce(ADDRESS_2)
tx4 = vm.create_unsigned_transaction(
nonce=nonce,
gas_price=100,
gas=100000,
to=ADDRESS_4,
value=1000000000000000000,
data=data,
)
signed_tx4 = tx4.as_signed_transaction(ADDRESS_2_PRIVATE_KEY)
_, _, computation = chain.apply_transaction(signed_tx4)
# We have to finalize the block first in order to be able read the
# attributes that are important for the PoW algorithm
block = chain.get_vm().finalize_block(chain.get_block())
# based on mining_hash, block number and difficulty we can perform
# the actual Proof of Work (PoW) mechanism to mine the correct
# nonce and mix_hash for this block
nonce, mix_hash = mine_pow_nonce(
block.number,
block.header.mining_hash,
block.header.difficulty)
block = chain.mine_block(mix_hash=mix_hash, nonce=nonce)
vm = chain.get_vm()
print("########### transfer (from: ADDRESS_2, to: ADDRESS_4)###############")
print("BLOCK4 ADDRESS_1 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_1)))
print("BLOCK4 ADDRESS_2 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_2)))
print("BLOCK4 ADDRESS_3 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_3)))
print("BLOCK4 ADDRESS_4 BALANCE : {}".format(vm.state.account_db.get_balance(ADDRESS_4)))
print("-----------------------")
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up