From 1cd86205a231d1d15c8a4441f851a28d3ba45dbe Mon Sep 17 00:00:00 2001 From: Vinh Date: Thu, 23 Jan 2025 09:19:39 -0800 Subject: [PATCH 1/3] disallow manual trigger of completed tasks --- core/taskengine/engine.go | 4 ++++ core/taskengine/engine_test.go | 44 +++++++++++++++++++++++++++++++++- core/taskengine/errors.go | 1 + 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/core/taskengine/engine.go b/core/taskengine/engine.go index a93ee412..1fa24f34 100644 --- a/core/taskengine/engine.go +++ b/core/taskengine/engine.go @@ -566,6 +566,10 @@ func (n *Engine) TriggerTask(user *model.User, payload *avsproto.UserTriggerTask return nil, err } + if !task.Runable() { + return nil, grpcstatus.Errorf(codes.FailedPrecondition, TaskIsNotRunable) + } + if !task.OwnedBy(user.Address) { // only the owner of a task can trigger it return nil, grpcstatus.Errorf(codes.NotFound, TaskNotFoundError) diff --git a/core/taskengine/engine_test.go b/core/taskengine/engine_test.go index f0c568e5..df28b4ac 100644 --- a/core/taskengine/engine_test.go +++ b/core/taskengine/engine_test.go @@ -331,7 +331,6 @@ func TestTriggerSync(t *testing.T) { t.Errorf("invalid triggered block. expect 101 got %d", execution.TriggerMetadata.BlockNumber) } } - func TestTriggerAsync(t *testing.T) { db := testutil.TestMustDB() defer storage.Destroy(db.(*storage.BadgerStorage)) @@ -413,3 +412,46 @@ func TestTriggerAsync(t *testing.T) { t.Errorf("invalid execution status, expected completed but got %s", avsproto.TaskStatus_name[int32(executionStatus.Status)]) } } + +func TestTriggerCompletedTaskReturnError(t *testing.T) { + db := testutil.TestMustDB() + defer storage.Destroy(db.(*storage.BadgerStorage)) + + config := testutil.GetAggregatorConfig() + n := New(db, config, nil, testutil.GetLogger()) + + // Now create a test task + tr1 := testutil.RestTask() + tr1.Name = "t1" + tr1.MaxExecution = 1 + // salt 0 + tr1.SmartWalletAddress = "0x7c3a76086588230c7B3f4839A4c1F5BBafcd57C6" + result, _ := n.CreateTask(testutil.TestUser1(), tr1) + + resultTrigger, err := n.TriggerTask(testutil.TestUser1(), &avsproto.UserTriggerTaskReq{ + TaskId: result.Id, + TriggerMetadata: &avsproto.TriggerMetadata{ + BlockNumber: 101, + }, + IsBlocking: true, + }) + + if err != nil || resultTrigger == nil { + t.Errorf("expected trigger succesfully but got error: %s", err) + } + + fmt.Println(resultTrigger) + // Now the task has reach its max run, and canot run anymore + resultTrigger, err = n.TriggerTask(testutil.TestUser1(), &avsproto.UserTriggerTaskReq{ + TaskId: result.Id, + TriggerMetadata: &avsproto.TriggerMetadata{ + BlockNumber: 101, + }, + IsBlocking: true, + }) + + if err == nil || resultTrigger != nil { + t.Errorf("expect trigger error but succeed") + } + +} diff --git a/core/taskengine/errors.go b/core/taskengine/errors.go index 2d36a160..b23e7989 100644 --- a/core/taskengine/errors.go +++ b/core/taskengine/errors.go @@ -20,6 +20,7 @@ const ( TaskStorageCorruptedError = "task data storage is corrupted" TaskIDMissing = "Missing task id in request" + TaskIsNotRunable = "The workflow is not in a runable status, it has reached the limit execution or the expiration time" InvalidCursor = "cursor is not valid" InvalidPaginationParam = "item per page is not valid" From d739ec514aabbd08f9ef9cf97ba7b0b65b36c535 Mon Sep 17 00:00:00 2001 From: Vinh Date: Thu, 23 Jan 2025 09:20:51 -0800 Subject: [PATCH 2/3] add missing file --- model/task.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/model/task.go b/model/task.go index b5c08ea4..cf2ad8df 100644 --- a/model/task.go +++ b/model/task.go @@ -130,6 +130,18 @@ func (t *Task) OwnedBy(address common.Address) bool { return strings.EqualFold(t.Owner, address.Hex()) } +// A task is runable when both of these condition are matched +// 1. Its max execution has not reached +// 2. Its expiration time has not reached +func (t *Task) Runable() bool { + // When MaxExecution is 0, it is unlimited run + reachedMaxRun := t.MaxExecution > 0 && t.TotalExecution >= t.MaxExecution + + reachedExpiredTime := t.ExpiredAt > 0 && time.Unix(t.ExpiredAt, 0).Before(time.Now()) + + return !reachedMaxRun && !reachedExpiredTime +} + // Given a task key generated from Key(), extract the ID part func TaskKeyToId(key []byte) []byte { // <43-byte>:<43-byte>: From e88b519dabdaf56226391e6be37e249c67cd9dd1 Mon Sep 17 00:00:00 2001 From: Vinh Date: Thu, 23 Jan 2025 11:44:26 -0800 Subject: [PATCH 3/3] Rename --- core/taskengine/engine.go | 2 +- model/task.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/taskengine/engine.go b/core/taskengine/engine.go index 1fa24f34..ac19d333 100644 --- a/core/taskengine/engine.go +++ b/core/taskengine/engine.go @@ -566,7 +566,7 @@ func (n *Engine) TriggerTask(user *model.User, payload *avsproto.UserTriggerTask return nil, err } - if !task.Runable() { + if !task.IsRunable() { return nil, grpcstatus.Errorf(codes.FailedPrecondition, TaskIsNotRunable) } diff --git a/model/task.go b/model/task.go index cf2ad8df..df1eee59 100644 --- a/model/task.go +++ b/model/task.go @@ -133,7 +133,7 @@ func (t *Task) OwnedBy(address common.Address) bool { // A task is runable when both of these condition are matched // 1. Its max execution has not reached // 2. Its expiration time has not reached -func (t *Task) Runable() bool { +func (t *Task) IsRunable() bool { // When MaxExecution is 0, it is unlimited run reachedMaxRun := t.MaxExecution > 0 && t.TotalExecution >= t.MaxExecution