Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composable Transactions #182

Merged
merged 57 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b4540a0
[contract] composable transactions
kroggen Jun 15, 2022
7c3cff7
[brick] add multicall command
kroggen Jun 16, 2022
001cac1
[contract] add tests for composable transactions
kroggen Jun 16, 2022
67322af
[brick] update multicall command
kroggen Jun 24, 2022
08da940
[contract] update composable transactions
kroggen Jun 25, 2022
e973975
[contract] add functions to composable transactions
kroggen Jul 27, 2022
5c1def2
[contract] add 'break if' to composable transactions
kroggen Jul 27, 2022
1f12e79
[contract] new test cases for composable transactions
kroggen Aug 7, 2022
a3e3ca4
[contract] fix error messages on multicall code
kroggen Aug 15, 2022
cb4344a
[chain] fix recipient handling for multicall
kroggen Aug 16, 2022
14c6586
Merge branch 'develop' into feature/composable-transactions
kroggen Nov 25, 2023
fb5c39e
fix build
kroggen Nov 25, 2023
b27419d
use separate lua test file
kroggen Nov 25, 2023
54e792a
fix test
kroggen Nov 25, 2023
02a23d4
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Nov 25, 2023
cd2700f
update test
kroggen Nov 25, 2023
50066a6
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Dec 13, 2023
10ba464
fix build after state refactoring
kroggen Dec 13, 2023
713d0de
compile the multicall code on demand
kroggen Dec 14, 2023
dca3d14
add MULTICALL to aergo-protobuf
kroggen Dec 16, 2023
1848b80
aergocli: add multicall command
kroggen Dec 17, 2023
2326e1c
aergocli: compute nonce on shared function
kroggen Dec 17, 2023
05e6b7a
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Dec 18, 2023
482a116
fix segfault
kroggen Dec 18, 2023
b318de2
rename variables
kroggen Dec 18, 2023
0bb3a85
multicall: add integration tests
kroggen Dec 20, 2023
51d81fc
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Jan 30, 2024
345092f
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Jan 30, 2024
52f1ab8
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Feb 7, 2024
d54ca44
fix integration test
kroggen Feb 8, 2024
f406be7
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
kroggen Jul 9, 2024
db125ad
fix use of pcall on multicall script
kroggen Jul 9, 2024
0a4be26
rename multicall commands for better understanding
kroggen Jul 10, 2024
8dfd84e
store -> store result as
kroggen Jul 10, 2024
02a469d
add 'in' on 'for each' command
kroggen Jul 10, 2024
8ec2915
add variable '%my aergo balance%'
kroggen Jul 13, 2024
19db28f
convert amount without decimal separator to bignum
kroggen Jul 13, 2024
5d86e63
convert amount to bignum on assertion
kroggen Jul 13, 2024
fa935a6
store result of try call on call_succeeded variable
kroggen Jul 13, 2024
b37b843
fix integration test
kroggen Jul 14, 2024
c6c184a
rename 'pow' and 'sqrt'
kroggen Jul 14, 2024
f14bbd9
add '%my account address%' variable
kroggen Jul 14, 2024
7d832a2
accept single boolean on if and assert
kroggen Jul 14, 2024
bbfe8c0
conversion functions without arg: use last_result
kroggen Jul 14, 2024
05cc7e3
%last_result% -> %last result%
kroggen Jul 14, 2024
e8c7224
fix skip setting last result
kroggen Jul 15, 2024
4090a6e
remove 'get keys', exponentiate and 'square root'
kroggen Jul 16, 2024
584e0c1
fix integration tests for dpos and raft
kroggen Jul 17, 2024
4e4dd8c
end -> end if
kroggen Jul 17, 2024
2d445a1
test state rollback on multicall txns
kroggen Jul 21, 2024
38e8116
test state rollback on integration test
kroggen Jul 21, 2024
158c14a
get balance -> get aergo balance
kroggen Jul 21, 2024
c91e7b2
fix integration test
kroggen Jul 21, 2024
4ebaf9d
add new integration tests
kroggen Jul 22, 2024
dde3772
let contracts use multicall scripts
kroggen Jul 22, 2024
cd4ce33
test state recovery on contract multicall
kroggen Jul 22, 2024
9657eb8
integration tests for contract multicall
kroggen Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'topic/hardfork-v4' into feature/composable-transactions
  • Loading branch information
kroggen committed Dec 18, 2023
commit 05e6b7a9b89f206b325602a29e0c18c399c09323
2 changes: 1 addition & 1 deletion chain/chainhandle.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb

switch txBody.Type {
case types.TxType_NORMAL, types.TxType_TRANSFER, types.TxType_CALL, types.TxType_MULTICALL, types.TxType_DEPLOY, types.TxType_REDEPLOY:
rv, events, txFee, err = contract.Execute(execCtx, bs, cdb, tx.GetTx(), sender, receiver, bi, preloadService, false)
rv, events, txFee, err = contract.Execute(execCtx, bs, cdb, tx.GetTx(), sender, receiver, bi, executionMode, false)
sender.SubBalance(txFee)
case types.TxType_GOVERNANCE:
txFee = new(big.Int).SetUint64(0)
Expand Down
102 changes: 8 additions & 94 deletions contract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func Execute(
tx *types.Tx,
sender, receiver *state.AccountState,
bi *types.BlockHeaderInfo,
preloadService int,
executionMode int,
isFeeDelegation bool,
) (rv string, events []*types.Event, usedFee *big.Int, err error) {

Expand Down Expand Up @@ -80,11 +80,11 @@ func Execute(
}

// open the contract state
var contractState *state.ContractState
var contractState *statedb.ContractState
if isMultiCall {
contractState = state.GetMultiCallState(sender.AccountID(), sender.State())
contractState = statedb.GetMultiCallState(sender.ID(), sender.State())
} else {
contractState, err = state.OpenContractState(receiver.AccountID(), receiver.State(), bs.StateDB)
contractState, err = statedb.OpenContractState(receiver.ID(), receiver.State(), bs.StateDB)
}
if err != nil {
return
Expand All @@ -102,14 +102,14 @@ func Execute(
var ctrFee *big.Int

// create a new context
ctx := NewVmContext(execCtx, bs, cdb, sender, receiver, contractState, sender.ID(), tx.GetHash(), bi, "", true, false, receiver.RP(), executionMode, txAmount, gasLimit, isFeeDelegation)
ctx := NewVmContext(execCtx, bs, cdb, sender, receiver, contractState, sender.ID(), tx.GetHash(), bi, "", true, false, receiver.RP(), executionMode, txAmount, gasLimit, isFeeDelegation, isMultiCall)

// execute the transaction
if receiver.IsDeploy() {
rv, events, ctrFee, err = Create(contractState, txPayload, receiver.ID(), ctx)
} else {
// create a new context
ctx := NewVmContext(execCtx, bs, cdb, sender, receiver, contractState, sender.ID(), tx.GetHash(), bi, "", true, false, receiver.RP(), preloadService, txAmount, gasLimit, isFeeDelegation, isMultiCall)
rv, events, ctrFee, err = Call(contractState, txPayload, receiver.ID(), ctx)
}

// close the trace file
if ctx.traceFile != nil {
Expand Down Expand Up @@ -144,7 +144,7 @@ func Execute(

if !isMultiCall {
// save the contract state
err = state.StageContractState(contractState, bs.StateDB)
err = statedb.StageContractState(contractState, bs.StateDB)
if err != nil {
return "", events, usedFee, err
}
Expand All @@ -154,92 +154,6 @@ func Execute(
return rv, events, usedFee, nil
}

// send a request to preload an executor for the next tx
func RequestPreload(bs *state.BlockState, bi *types.BlockHeaderInfo, next, current *types.Tx, preloadService int) {
loadReqCh <- &preloadRequest{preloadService, bs, bi, next, current}
}

// the preloadWorker preloads an executor for the next tx
func preloadWorker() {
// infinite loop
for {
var err error

// wait for a preload request
request := <-loadReqCh
// get the reply channel for this request
replyCh := preloaders[request.preloadService].replyCh

// if there are more than 2 requests waiting for a reply, close the oldest one
if len(replyCh) > 2 {
select {
case preload := <-replyCh:
preload.ex.close()
default:
}
}

bs := request.bs
tx := request.next // the tx to preload the executor for
txBody := tx.GetBody()
recipient := txBody.Recipient

// only preload an executor for a normal, transfer, call or fee delegation tx
if (txBody.Type != types.TxType_NORMAL &&
txBody.Type != types.TxType_TRANSFER &&
txBody.Type != types.TxType_CALL &&
txBody.Type != types.TxType_FEEDELEGATION) ||
len(recipient) == 0 {
continue
}

// if the tx currently being executed is a redeploy
if request.current.GetBody().Type == types.TxType_REDEPLOY {
// if the next tx is a call to the redeployed contract
currentTxBody := request.current.GetBody()
if bytes.Equal(recipient, currentTxBody.Recipient) {
// do not preload an executor for a contract that is being redeployed
replyCh <- &preloadReply{tx, nil, nil}
continue
}
}

// get the state of the recipient
receiver, err := state.GetAccountState(recipient, bs.StateDB)
if err != nil {
replyCh <- &preloadReply{tx, nil, err}
continue
}

// when deploy and call in same block and not deployed yet
if receiver.IsNew() || !receiver.IsContract() {
// do not preload an executor for a contract that is not deployed yet
replyCh <- &preloadReply{tx, nil, nil}
continue
}

// open the contract state
contractState, err := state.OpenContractState(receiver.AccountID(), receiver.State(), bs.StateDB)
if err != nil {
replyCh <- &preloadReply{tx, nil, err}
continue
}

// create a new context
// FIXME need valid context
ctx := NewVmContext(context.Background(), bs, nil, nil, receiver, contractState, txBody.GetAccount(), tx.GetHash(), request.bi, "", false, false, receiver.RP(), request.preloadService, txBody.GetAmountBigInt(), txBody.GetGasLimit(), txBody.Type == types.TxType_FEEDELEGATION, txBody.Type == types.TxType_MULTICALL)

// load a new executor
ex, err := PreloadExecutor(bs, contractState, txBody.Payload, receiver.ID(), ctx)
if ex == nil && ctx.traceFile != nil {
ctx.traceFile.Close()
}

// send reply with executor
replyCh <- &preloadReply{tx, ex, err}
}
}

// check if the tx is valid and if the code should be executed
func checkExecution(txType types.TxType, amount *big.Int, payloadSize int, version int32, isDeploy, isContract bool) (do_execute bool, err error) {

Expand Down
117 changes: 55 additions & 62 deletions contract/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,24 +134,29 @@ func InitContext(numCtx int) {
contexts = make([]*vmContext, maxContext)
}

func NewVmContext(execCtx context.Context, blockState *state.BlockState, cdb ChainAccessor, sender, receiver *state.AccountState,
contractState *statedb.ContractState, senderID, txHash []byte, bi *types.BlockHeaderInfo, node string, confirmed,
query bool, rp uint64, executionMode int, amount *big.Int, gasLimit uint64, feeDelegation bool) *vmContext {

func getTraceFile(blkno uint64, tx []byte) *os.File {
f, _ := os.OpenFile(fmt.Sprintf("%s%s%d.trace", os.TempDir(), string(os.PathSeparator), blkno), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if f != nil {
_, _ = f.WriteString(fmt.Sprintf("[START TX]: %s\n", base58.Encode(tx)))
}
return f
}

func NewVmContext(execCtx context.Context, blockState *state.BlockState, cdb ChainAccessor, sender, receiver *state.AccountState, contractState *state.ContractState, senderID, txHash []byte, bi *types.BlockHeaderInfo, node string, confirmed, query bool, rp uint64, service int, amount *big.Int, gasLimit uint64, feeDelegation bool, isMultiCall bool) *vmContext {
func NewVmContext(
execCtx context.Context,
blockState *state.BlockState,
cdb ChainAccessor,
sender, receiver *state.AccountState,
contractState *statedb.ContractState,
senderID,
txHash []byte,
bi *types.BlockHeaderInfo,
node string,
confirmed, query bool,
rp uint64,
executionMode int,
amount *big.Int,
gasLimit uint64,
feeDelegation, isMultiCall bool,
) *vmContext {

cs := &callState{ctrState: contractState, curState: receiver.State()}
csReceiver := &callState{ctrState: contractState, accState: receiver}
csSender := &callState{accState: sender}

ctx := &vmContext{
curContract: newContractInfo(cs, senderID, receiver.ID(), rp, amount),
curContract: newContractInfo(csReceiver, senderID, receiver.ID(), rp, amount),
bs: blockState,
cdb: cdb,
origin: senderID,
Expand All @@ -170,9 +175,9 @@ func NewVmContext(execCtx context.Context, blockState *state.BlockState, cdb Cha

// init call state
ctx.callState = make(map[types.AccountID]*callState)
ctx.callState[receiver.AccountID()] = cs
ctx.callState[receiver.AccountID()] = csReceiver
if sender != nil && sender != receiver {
ctx.callState[sender.AccountID()] = &callState{curState: sender.State()}
ctx.callState[sender.AccountID()] = csSender
}
if TraceBlockNo != 0 && TraceBlockNo == ctx.blockInfo.No {
ctx.traceFile = getTraceFile(ctx.blockInfo.No, txHash)
Expand Down Expand Up @@ -748,6 +753,35 @@ func (ce *executor) gas() uint64 {
return uint64(C.lua_gasget(ce.L))
}

func (ce *executor) vmLoadCode(id []byte) {
var chunkId *C.char
if ce.ctx.blockInfo.ForkVersion >= 3 {
chunkId = C.CString("@" + types.EncodeAddress(id))
} else {
chunkId = C.CString(hex.Encode(id))
}
defer C.free(unsafe.Pointer(chunkId))
if cErrMsg := C.vm_loadbuff(
ce.L,
(*C.char)(unsafe.Pointer(&ce.code[0])),
C.size_t(len(ce.code)),
chunkId,
ce.ctx.service-C.int(maxContext),
); cErrMsg != nil {
errMsg := C.GoString(cErrMsg)
ce.err = errors.New(errMsg)
ctrLgr.Debug().Err(ce.err).Str("contract", types.EncodeAddress(id)).Msg("failed to load code")
}
}

func (ce *executor) vmLoadCall() {
if cErrMsg := C.vm_loadcall(ce.L); cErrMsg != nil {
errMsg := C.GoString(cErrMsg)
ce.err = errors.New(errMsg)
}
C.luaL_set_service(ce.L, ce.ctx.service)
}

func getMultiCallInfo(ci *types.CallInfo, payload []byte) error {
payload = append([]byte{'['}, payload...)
payload = append(payload, ']')
Expand Down Expand Up @@ -1189,7 +1223,8 @@ func getCode(contractState *statedb.ContractState, bs *state.BlockState) ([]byte
return code, nil
}

func getContract(contractState *state.ContractState, bs *state.BlockState) []byte {
func getContract(contractState *statedb.ContractState, bs *state.BlockState) []byte {
// the code from multicall is not loaded, because there is no code hash
if len(contractState.GetCodeHash()) == 0 {
return nil
}
Expand All @@ -1200,7 +1235,7 @@ func getContract(contractState *state.ContractState, bs *state.BlockState) []byt
return luacUtil.LuaCode(code).ByteCode()
}

func getMultiCallContract(contractState *state.ContractState) []byte {
func getMultiCallContract(contractState *statedb.ContractState) []byte {

if multicall_payload == nil {
var err error
Expand All @@ -1220,7 +1255,7 @@ func getMultiCallContract(contractState *state.ContractState) []byte {
return multicall_payload.ByteCode()
}

func GetABI(contractState *state.ContractState, bs *state.BlockState) (*types.ABI, error) {
func GetABI(contractState *statedb.ContractState, bs *state.BlockState) (*types.ABI, error) {
var abi *types.ABI

abi = bs.GetABI(contractState.GetAccountID())
Expand Down Expand Up @@ -1275,45 +1310,3 @@ func Compile(code string, parent *LState) (luacUtil.LuaCode, error) {
}
return byteCodeAbi, nil
}

func vmAutoload(L *LState, funcName string) bool {
s := C.CString(funcName)
loaded := C.vm_autoload(L, s)
C.free(unsafe.Pointer(s))
return loaded != C.int(0)
}

func (ce *executor) vmLoadCode(id []byte) {
var chunkId *C.char
if ce.ctx.blockInfo.ForkVersion >= 3 {
if ce.ctx.isMultiCall {
chunkId = C.CString("@multicall")
} else {
chunkId = C.CString("@" + types.EncodeAddress(id))
}
} else {
chunkId = C.CString(hex.Encode(id))
}
defer C.free(unsafe.Pointer(chunkId))
if cErrMsg := C.vm_loadbuff(
ce.L,
(*C.char)(unsafe.Pointer(&ce.code[0])),
C.size_t(len(ce.code)),
chunkId,
ce.ctx.service-C.int(maxContext),
); cErrMsg != nil {
errMsg := C.GoString(cErrMsg)
ce.err = errors.New(errMsg)
ctrLgr.Debug().Err(ce.err).Str("contract", types.EncodeAddress(id)).Msg("failed to load code")
}
}

func (ce *executor) vmLoadCall() {
if cErrMsg := C.vm_loadcall(
ce.L,
); cErrMsg != nil {
errMsg := C.GoString(cErrMsg)
ce.err = errors.New(errMsg)
}
C.luaL_set_service(ce.L, ce.ctx.service)
}
16 changes: 8 additions & 8 deletions contract/vm_dummy/vm_dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,11 +474,11 @@ func contractFrame(l luaTxContract, bs *state.BlockState, cdb contract.ChainAcce
return err
}

var eContractState *state.ContractState
var eContractState *statedb.ContractState
if l.isMultiCall() {
eContractState = state.GetMultiCallState(creatorId, creatorState.State())
eContractState = statedb.GetMultiCallState(l.sender(), creatorState.State())
} else {
eContractState, err = state.OpenContractState(contractId, contractState.State(), bs.StateDB)
eContractState, err = statedb.OpenContractState(l.recipient(), contractState.State(), bs.StateDB)
}
if err != nil {
return err
Expand All @@ -503,9 +503,9 @@ func contractFrame(l luaTxContract, bs *state.BlockState, cdb contract.ChainAcce
}
}

if contractId != creatorId {
creatorState.SubBalance(l.amount())
contractState.AddBalance(l.amount())
err = state.SendBalance(creatorState, contractState, l.amount())
if err != nil {
return err
}

rv, events, cFee, err := run(creatorState, contractState, contractId, eContractState)
Expand Down Expand Up @@ -633,7 +633,7 @@ func (l *luaTxCall) Fail(expectedErr string) *luaTxCall {

func (l *luaTxCall) run(execCtx context.Context, bs *state.BlockState, bc *DummyChain, bi *types.BlockHeaderInfo, receiptTx db.Transaction) error {
err := contractFrame(l, bs, bc, receiptTx,
func(sender, contractV *state.AccountState, contractId types.AccountID, eContractState *state.ContractState) (string, []*types.Event, *big.Int, error) {
func(sender, contractV *state.AccountState, contractId types.AccountID, eContractState *statedb.ContractState) (string, []*types.Event, *big.Int, error) {

ctx := contract.NewVmContext(execCtx, bs, bc, sender, contractV, eContractState, sender.ID(), l.Hash(), bi, "", true, false, contractV.State().SqlRecoveryPoint, contract.BlockFactory, l.amount(), math.MaxUint64, l.feeDelegate, l.multiCall)

Expand All @@ -643,7 +643,7 @@ func (l *luaTxCall) run(execCtx context.Context, bs *state.BlockState, bc *Dummy
}

if !ctx.IsMultiCall() {
err = state.StageContractState(eContractState, bs.StateDB)
err = statedb.StageContractState(eContractState, bs.StateDB)
if err != nil {
return "", nil, ctrFee, err
}
Expand Down
6 changes: 4 additions & 2 deletions state/statedb/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,17 @@ func (cs *ContractState) cache() *stateBuffer {
//---------------------------------------------------------------//
// global functions

func GetMultiCallState(aid types.AccountID, st *types.State) (*ContractState) {
func GetMultiCallState(id []byte, st *types.State) (*ContractState) {
aid := types.ToAccountID(id)
res := &ContractState{
State: st,
account: aid,
}
return res
}

func OpenContractStateAccount(aid types.AccountID, states *statedb.StateDB) (*ContractState, error) {
func OpenContractStateAccount(id []byte, states *StateDB) (*ContractState, error) {
aid := types.ToAccountID(id)
st, err := states.GetAccountState(aid)
if err != nil {
return nil, err
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.