diff --git a/lib/block/operation.go b/lib/block/operation.go index 0653bbd7a..dd5c62b7b 100644 --- a/lib/block/operation.go +++ b/lib/block/operation.go @@ -26,11 +26,14 @@ type BlockOperation struct { Type operation.OperationType `json:"type"` Source string `json:"source"` + Target string `json:"target"` Body []byte `json:"body"` Height uint64 `json:"block_height"` - // transaction will be used only for `Save` time. + // bellows will be used only for `Save` time. transaction transaction.Transaction + operation operation.Operation + linked string isSaved bool } @@ -47,6 +50,18 @@ func NewBlockOperationFromOperation(op operation.Operation, tx transaction.Trans opHash := op.MakeHashString() txHash := tx.GetHash() + target := "" + if pop, ok := op.B.(operation.Targetable); ok { + target = pop.TargetAddress() + } + + linked := "" + if createAccount, ok := op.B.(*operation.CreateAccount); ok { + if createAccount.Linked != "" { + linked = createAccount.Linked + } + } + return BlockOperation{ Hash: NewBlockOperationKey(opHash, txHash), @@ -55,13 +70,30 @@ func NewBlockOperationFromOperation(op operation.Operation, tx transaction.Trans Type: op.H.Type, Source: tx.B.Source, + Target: target, Body: body, Height: blockHeight, transaction: tx, + operation: op, + linked: linked, }, nil } +func (bo *BlockOperation) hasTarget() bool { + if bo.Target != "" { + return true + } + return false +} + +func (bo *BlockOperation) targetIsLinked() bool { + if bo.hasTarget() && bo.linked != "" { + return true + } + return false +} + func (bo *BlockOperation) Save(st *storage.LevelDBBackend) (err error) { if bo.isSaved { return errors.AlreadySaved @@ -82,35 +114,44 @@ func (bo *BlockOperation) Save(st *storage.LevelDBBackend) (err error) { if err = st.New(bo.NewBlockOperationTxHashKey(), bo.Hash); err != nil { return } + if err = st.New(bo.NewBlockOperationSourceKey(), bo.Hash); err != nil { return } - if err = st.New(bo.NewBlockOperationSourceAndTypeKey(), bo.Hash); err != nil { return } + if err = st.New(bo.NewBlockOperationPeersKey(bo.Source), bo.Hash); err != nil { + return + } + if err = st.New(bo.NewBlockOperationPeersAndTypeKey(bo.Source), bo.Hash); err != nil { + return + } if err = st.New(bo.NewBlockOperationBlockHeightKey(), bo.Hash); err != nil { return } - var body operation.Body - var casted operation.CreateAccount - var ok bool + if bo.hasTarget() { + if err = st.New(bo.NewBlockOperationTargetKey(bo.Target), bo.Hash); err != nil { + return + } + if err = st.New(bo.NewBlockOperationTargetAndTypeKey(bo.Target), bo.Hash); err != nil { + return + } + if err = st.New(bo.NewBlockOperationPeersKey(bo.Target), bo.Hash); err != nil { + return + } + if err = st.New(bo.NewBlockOperationPeersAndTypeKey(bo.Target), bo.Hash); err != nil { + return + } + } - if bo.Type == operation.TypeCreateAccount { - if body, err = operation.UnmarshalBodyJSON(bo.Type, bo.Body); err != nil { + if bo.targetIsLinked() { + if err = st.New(GetBlockOperationCreateFrozenKey(bo.Target, bo.Height), bo.Hash); err != nil { return err } - if casted, ok = body.(operation.CreateAccount); !ok { - return errors.TypeOperationBodyNotMatched - } - if casted.Linked != "" { - if err = st.New(GetBlockOperationCreateFrozenKey(casted.Target, bo.Height), bo.Hash); err != nil { - return err - } - if err = st.New(bo.NewBlockOperationFrozenLinkedKey(casted.Linked), bo.Hash); err != nil { - return err - } + if err = st.New(bo.NewBlockOperationFrozenLinkedKey(bo.linked), bo.Hash); err != nil { + return err } } @@ -154,13 +195,29 @@ func GetBlockOperationKeyPrefixSource(source string) string { } func GetBlockOperationKeyPrefixSourceAndType(source string, ty operation.OperationType) string { - return fmt.Sprintf("%s%s%s-", common.BlockOperationPrefixSource, source, string(ty)) + return fmt.Sprintf("%s%s%s-", common.BlockOperationPrefixTypeSource, string(ty), source) } func GetBlockOperationKeyPrefixBlockHeight(height uint64) string { return fmt.Sprintf("%s%s-", common.BlockOperationPrefixBlockHeight, common.EncodeUint64ToByteSlice(height)) } +func GetBlockOperationKeyPrefixTarget(target string) string { + return fmt.Sprintf("%s%s-", common.BlockOperationPrefixTarget, target) +} + +func GetBlockOperationKeyPrefixTargetAndType(target string, ty operation.OperationType) string { + return fmt.Sprintf("%s%s%s-", common.BlockOperationPrefixTypeTarget, string(ty), target) +} + +func GetBlockOperationKeyPrefixPeers(addr string) string { + return fmt.Sprintf("%s%s-", common.BlockOperationPrefixPeers, addr) +} + +func GetBlockOperationKeyPrefixPeersAndType(addr string, ty operation.OperationType) string { + return fmt.Sprintf("%s%s%s-", common.BlockOperationPrefixTypePeers, string(ty), addr) +} + func (bo BlockOperation) NewBlockOperationTxHashKey() string { return fmt.Sprintf( "%s%s%s%s", @@ -198,7 +255,45 @@ func (bo BlockOperation) NewBlockOperationSourceAndTypeKey() string { common.GetUniqueIDFromUUID(), ) } +func (bo BlockOperation) NewBlockOperationTargetKey(target string) string { + return fmt.Sprintf( + "%s%s%s%s", + GetBlockOperationKeyPrefixTarget(target), + common.EncodeUint64ToByteSlice(bo.Height), + common.EncodeUint64ToByteSlice(bo.transaction.B.SequenceID), + common.GetUniqueIDFromUUID(), + ) +} + +func (bo BlockOperation) NewBlockOperationTargetAndTypeKey(target string) string { + return fmt.Sprintf( + "%s%s%s%s", + GetBlockOperationKeyPrefixTargetAndType(target, bo.Type), + common.EncodeUint64ToByteSlice(bo.Height), + common.EncodeUint64ToByteSlice(bo.transaction.B.SequenceID), + common.GetUniqueIDFromUUID(), + ) +} + +func (bo BlockOperation) NewBlockOperationPeersKey(addr string) string { + return fmt.Sprintf( + "%s%s%s%s", + GetBlockOperationKeyPrefixPeers(addr), + common.EncodeUint64ToByteSlice(bo.Height), + common.EncodeUint64ToByteSlice(bo.transaction.B.SequenceID), + common.GetUniqueIDFromUUID(), + ) +} +func (bo BlockOperation) NewBlockOperationPeersAndTypeKey(addr string) string { + return fmt.Sprintf( + "%s%s%s%s", + GetBlockOperationKeyPrefixPeersAndType(addr, bo.Type), + common.EncodeUint64ToByteSlice(bo.Height), + common.EncodeUint64ToByteSlice(bo.transaction.B.SequenceID), + common.GetUniqueIDFromUUID(), + ) +} func (bo BlockOperation) NewBlockOperationBlockHeightKey() string { return fmt.Sprintf( "%s%s%s", @@ -294,6 +389,40 @@ func GetBlockOperationsBySourceAndType(st *storage.LevelDBBackend, source string return LoadBlockOperationsInsideIterator(st, iterFunc, closeFunc) } +func GetBlockOperationsByTarget(st *storage.LevelDBBackend, target string, options storage.ListOptions) ( + func() (BlockOperation, bool, []byte), + func(), +) { + iterFunc, closeFunc := st.GetIterator(GetBlockOperationKeyPrefixTarget(target), options) + + return LoadBlockOperationsInsideIterator(st, iterFunc, closeFunc) +} + +func GetBlockOperationsByTargetAndType(st *storage.LevelDBBackend, target string, ty operation.OperationType, options storage.ListOptions) ( + func() (BlockOperation, bool, []byte), + func(), +) { + iterFunc, closeFunc := st.GetIterator(GetBlockOperationKeyPrefixTargetAndType(target, ty), options) + return LoadBlockOperationsInsideIterator(st, iterFunc, closeFunc) +} + +func GetBlockOperationsByPeers(st *storage.LevelDBBackend, addr string, options storage.ListOptions) ( + func() (BlockOperation, bool, []byte), + func(), +) { + iterFunc, closeFunc := st.GetIterator(GetBlockOperationKeyPrefixPeers(addr), options) + + return LoadBlockOperationsInsideIterator(st, iterFunc, closeFunc) +} + +func GetBlockOperationsByPeersAndType(st *storage.LevelDBBackend, addr string, ty operation.OperationType, options storage.ListOptions) ( + func() (BlockOperation, bool, []byte), + func(), +) { + iterFunc, closeFunc := st.GetIterator(GetBlockOperationKeyPrefixPeersAndType(addr, ty), options) + return LoadBlockOperationsInsideIterator(st, iterFunc, closeFunc) +} + func GetBlockOperationsByBlockHeight(st *storage.LevelDBBackend, height uint64, options storage.ListOptions) ( func() (BlockOperation, bool, []byte), func(), diff --git a/lib/client/response.go b/lib/client/response.go index 4f08df721..31a16db86 100644 --- a/lib/client/response.go +++ b/lib/client/response.go @@ -112,6 +112,7 @@ type Operation struct { } `json:"_links"` Hash string `json:"hash"` Source string `json:"source"` + Target string `json:"target"` Type string `json:"type"` Body interface{} `json:"body"` } diff --git a/lib/common/prefix.go b/lib/common/prefix.go index eecfb2d6f..8457c96d1 100644 --- a/lib/common/prefix.go +++ b/lib/common/prefix.go @@ -15,9 +15,12 @@ const ( BlockOperationPrefixSource = string(0x22) BlockOperationPrefixTarget = string(0x23) BlockOperationPrefixPeers = string(0x24) - BlockOperationPrefixCreateFrozen = string(0x25) - BlockOperationPrefixFrozenLinked = string(0x26) - BlockOperationPrefixBlockHeight = string(0x27) + BlockOperationPrefixTypeSource = string(0x25) + BlockOperationPrefixTypeTarget = string(0x26) + BlockOperationPrefixTypePeers = string(0x27) + BlockOperationPrefixCreateFrozen = string(0x28) + BlockOperationPrefixFrozenLinked = string(0x29) + BlockOperationPrefixBlockHeight = string(0x2A) BlockAccountPrefixAddress = string(0x30) BlockAccountPrefixCreated = string(0x31) BlockAccountSequenceIDPrefix = string(0x32) diff --git a/lib/node/runner/api/api.go b/lib/node/runner/api/api.go index 171d4d14e..f70234e8f 100644 --- a/lib/node/runner/api/api.go +++ b/lib/node/runner/api/api.go @@ -80,7 +80,7 @@ func TriggerEvent(st *storage.LevelDBBackend, transactions []*transaction.Transa opEvent += " " + observer.NewCondition(observer.ResourceOperation, observer.KeySource, source).Event() opEvent += " " + observer.Conditions{observer.NewCondition(observer.ResourceOperation, observer.KeySource, source), observer.NewCondition(observer.ResourceOperation, observer.KeyType, string(op.H.Type))}.Event() - if pop, ok := op.B.(operation.Tagetable); ok { + if pop, ok := op.B.(operation.Targetable); ok { target := pop.TargetAddress() accountMap[target] = struct{}{} txEvent += " " + observer.NewCondition(observer.ResourceTransaction, observer.KeyTarget, target).Event() diff --git a/lib/node/runner/api/base_test.go b/lib/node/runner/api/base_test.go index 90d3ecc2f..0402dbe53 100644 --- a/lib/node/runner/api/base_test.go +++ b/lib/node/runner/api/base_test.go @@ -38,8 +38,8 @@ func prepareAPIServer() (*httptest.Server, *storage.LevelDBBackend) { return ts, storage } -func prepareOps(storage *storage.LevelDBBackend, count int) (*keypair.Full, []block.BlockOperation) { - kp, btList := prepareTxs(storage, count) +func prepareOps(storage *storage.LevelDBBackend, count int) (*keypair.Full, *keypair.Full, []block.BlockOperation) { + kp, kpTarget, btList := prepareTxs(storage, count) var boList []block.BlockOperation for _, bt := range btList { bo, err := block.GetBlockOperation(storage, bt.Operations[0]) @@ -49,7 +49,7 @@ func prepareOps(storage *storage.LevelDBBackend, count int) (*keypair.Full, []bl boList = append(boList, bo) } - return kp, boList + return kp, kpTarget, boList } func prepareOpsWithoutSave(count int, st *storage.LevelDBBackend) (*keypair.Full, block.Block, []block.BlockOperation) { kp := keypair.Random() @@ -93,13 +93,14 @@ func prepareBlkTxOpWithoutSave(st *storage.LevelDBBackend) (*keypair.Full, block return kp, theBlock, bt, bo } -func prepareTxs(storage *storage.LevelDBBackend, count int) (*keypair.Full, []block.BlockTransaction) { +func prepareTxs(storage *storage.LevelDBBackend, count int) (*keypair.Full, *keypair.Full, []block.BlockTransaction) { kp := keypair.Random() + kpTarget := keypair.Random() var txs []transaction.Transaction var txHashes []string var btList []block.BlockTransaction for i := 0; i < count; i++ { - tx := transaction.TestMakeTransactionWithKeypair(networkID, 1, kp) + tx := transaction.TestMakeTransactionWithKeypair(networkID, 1, kp, kpTarget) txs = append(txs, tx) txHashes = append(txHashes, tx.GetHash()) } @@ -110,11 +111,11 @@ func prepareTxs(storage *storage.LevelDBBackend, count int) (*keypair.Full, []bl bt := block.NewBlockTransactionFromTransaction(theBlock.Hash, theBlock.Height, theBlock.ProposedTime, tx) bt.MustSave(storage) if err := bt.SaveBlockOperations(storage); err != nil { - return nil, nil + return nil, nil, nil } btList = append(btList, bt) } - return kp, btList + return kp, kpTarget, btList } func prepareTxsWithoutSave(count int, st *storage.LevelDBBackend) (*keypair.Full, []block.BlockTransaction) { diff --git a/lib/node/runner/api/operation.go b/lib/node/runner/api/operation.go index 20cbc9025..2c9c6f0ab 100644 --- a/lib/node/runner/api/operation.go +++ b/lib/node/runner/api/operation.go @@ -39,9 +39,9 @@ func (api NetworkHandlerAPI) GetOperationsByAccountHandler(w http.ResponseWriter var iterFunc func() (block.BlockOperation, bool, []byte) var closeFunc func() if len(oType) > 0 { - iterFunc, closeFunc = block.GetBlockOperationsBySourceAndType(api.storage, address, oType, options) + iterFunc, closeFunc = block.GetBlockOperationsByPeersAndType(api.storage, address, oType, options) } else { - iterFunc, closeFunc = block.GetBlockOperationsBySource(api.storage, address, options) + iterFunc, closeFunc = block.GetBlockOperationsByPeers(api.storage, address, options) } for { t, hasNext, c := iterFunc() diff --git a/lib/node/runner/api/operation_test.go b/lib/node/runner/api/operation_test.go index beb0cb021..9adc8609e 100644 --- a/lib/node/runner/api/operation_test.go +++ b/lib/node/runner/api/operation_test.go @@ -20,7 +20,7 @@ func TestGetOperationsByAccountHandler(t *testing.T) { defer storage.Close() defer ts.Close() - kp, boList := prepareOps(storage, 10) + kp, kpTarget, boList := prepareOps(storage, 10) url := strings.Replace(GetAccountOperationsHandlerPattern, "{id}", kp.Address(), -1) { @@ -36,9 +36,36 @@ func TestGetOperationsByAccountHandler(t *testing.T) { ba := block.NewBlockAccount(kp.Address(), common.Amount(common.BaseReserve)) ba.MustSave(storage) } + { + ba := block.NewBlockAccount(kpTarget.Address(), common.Amount(common.BaseReserve)) + ba.MustSave(storage) + } + // Do a Request for source account + { + respBody := request(ts, url, false) + defer respBody.Close() + reader := bufio.NewReader(respBody) + readByte, err := ioutil.ReadAll(reader) + require.NoError(t, err) + + recv := make(map[string]interface{}) + json.Unmarshal(readByte, &recv) + records := recv["_embedded"].(map[string]interface{})["records"].([]interface{}) + + require.Equal(t, len(boList), len(records), "length is not same") + + for i, r := range records { + bt := r.(map[string]interface{}) + hash := bt["hash"].(string) + + require.Equal(t, hash, boList[i].Hash, "hash is not same") + } + } + + // Do a Request for target account + url = strings.Replace(GetAccountOperationsHandlerPattern, "{id}", kpTarget.Address(), -1) { - // Do a Request respBody := request(ts, url, false) defer respBody.Close() reader := bufio.NewReader(respBody) @@ -69,11 +96,17 @@ func TestGetOperationsByAccountHandlerWithType(t *testing.T) { defer storage.Close() defer ts.Close() - kp, boList := prepareOps(storage, 10) - ba := block.NewBlockAccount(kp.Address(), common.Amount(common.BaseReserve)) - ba.MustSave(storage) + kp, kpTarget, boList := prepareOps(storage, 10) + { + ba := block.NewBlockAccount(kp.Address(), common.Amount(common.BaseReserve)) + ba.MustSave(storage) + } + { + ba := block.NewBlockAccount(kpTarget.Address(), common.Amount(common.BaseReserve)) + ba.MustSave(storage) + } - // Do a Request + // Do a Request for Source url := strings.Replace(GetAccountOperationsHandlerPattern, "{id}", kp.Address(), -1) { url := url + "?type=" + string(operation.TypeCreateAccount) @@ -117,4 +150,48 @@ func TestGetOperationsByAccountHandlerWithType(t *testing.T) { } } + // Do a Request for Target + url = strings.Replace(GetAccountOperationsHandlerPattern, "{id}", kpTarget.Address(), -1) + { + url := url + "?type=" + string(operation.TypeCreateAccount) + respBody := request(ts, url, false) + defer respBody.Close() + reader := bufio.NewReader(respBody) + + readByte, err := ioutil.ReadAll(reader) + require.NoError(t, err) + + recv := make(map[string]interface{}) + json.Unmarshal(readByte, &recv) + records := recv["_embedded"].(map[string]interface{})["records"] + require.Nil(t, records) + } + + { + url := url + "?type=" + string(operation.TypePayment) + respBody := request(ts, url, false) + defer respBody.Close() + reader := bufio.NewReader(respBody) + + readByte, err := ioutil.ReadAll(reader) + require.NoError(t, err) + + recv := make(map[string]interface{}) + json.Unmarshal(readByte, &recv) + records := recv["_embedded"].(map[string]interface{})["records"].([]interface{}) + + require.Equal(t, len(boList), len(records), "length is not same") + + for i, r := range records { + bt := r.(map[string]interface{}) + hash := bt["hash"].(string) + + require.Equal(t, hash, boList[i].Hash, "hash is not same") + + blk, _ := block.GetBlockByHeight(storage, uint64(bt["block_height"].(float64))) + require.Equal(t, blk.ProposedTime, bt["proposed_time"].(string)) + require.Equal(t, blk.Confirmed, bt["confirmed"].(string)) + } + } + } diff --git a/lib/node/runner/api/resource/operation.go b/lib/node/runner/api/resource/operation.go index 10acb731c..6dc75e064 100644 --- a/lib/node/runner/api/resource/operation.go +++ b/lib/node/runner/api/resource/operation.go @@ -32,6 +32,7 @@ func (o Operation) GetMap() hal.Entry { entry := hal.Entry{ "hash": o.bo.Hash, "source": o.bo.Source, + "target": o.bo.Target, "type": o.bo.Type, "tx_hash": o.bo.TxHash, "body": body, diff --git a/lib/node/runner/api/transaction_test.go b/lib/node/runner/api/transaction_test.go index f4336c026..c0480f275 100644 --- a/lib/node/runner/api/transaction_test.go +++ b/lib/node/runner/api/transaction_test.go @@ -95,7 +95,7 @@ func TestGetTransactionsHandler(t *testing.T) { defer storage.Close() defer ts.Close() - _, btList := prepareTxs(storage, 10) + _, _, btList := prepareTxs(storage, 10) var reader *bufio.Reader { @@ -132,7 +132,7 @@ func TestGetTransactionsByAccountHandler(t *testing.T) { defer storage.Close() defer ts.Close() - kp, btList := prepareTxs(storage, 10) + kp, _, btList := prepareTxs(storage, 10) // Do a Request var reader *bufio.Reader @@ -170,7 +170,7 @@ func TestGetTransactionsHandlerPage(t *testing.T) { defer storage.Close() defer ts.Close() - _, btList := prepareTxs(storage, 10) + _, _, btList := prepareTxs(storage, 10) requestFunction := func(url string) ([]interface{}, map[string]interface{}) { respBody := request(ts, url, false) diff --git a/lib/node/runner/api/tx_operations_test.go b/lib/node/runner/api/tx_operations_test.go index 3459fcc96..45953a0af 100644 --- a/lib/node/runner/api/tx_operations_test.go +++ b/lib/node/runner/api/tx_operations_test.go @@ -18,7 +18,7 @@ func TestGetOperationsByTxHashHandler(t *testing.T) { defer storage.Close() defer ts.Close() - _, btList := prepareTxs(storage, 1) + _, _, btList := prepareTxs(storage, 1) bt := btList[0] { // unknown transaction diff --git a/lib/transaction/operation/operation.go b/lib/transaction/operation/operation.go index e61966dff..ffceddb90 100644 --- a/lib/transaction/operation/operation.go +++ b/lib/transaction/operation/operation.go @@ -109,7 +109,7 @@ type Payable interface { GetAmount() common.Amount } -type Tagetable interface { +type Targetable interface { TargetAddress() string } diff --git a/tests/client/const.go b/tests/client/const.go index 5544b64e7..9a6c816fb 100644 --- a/tests/client/const.go +++ b/tests/client/const.go @@ -1,5 +1,3 @@ -// +build client_integration_tests - package client const ( diff --git a/tests/client/freezing_test.go b/tests/client/freezing_test.go index 34fcff587..af752908b 100644 --- a/tests/client/freezing_test.go +++ b/tests/client/freezing_test.go @@ -98,7 +98,7 @@ func TestFreezingAccount(t *testing.T) { // Refund { - time.Sleep(time.Second * 10 ) //wait for block period + time.Sleep(time.Second * 10) //wait for block period account2Account, err := c.LoadAccount(account2Addr) require.NoError(t, err) diff --git a/tests/client/operation_test.go b/tests/client/operation_test.go new file mode 100644 index 000000000..804bcf669 --- /dev/null +++ b/tests/client/operation_test.go @@ -0,0 +1,60 @@ +// +build client_integration_tests + +package client + +import ( + "boscoin.io/sebak/lib/client" + "github.com/stretchr/testify/require" + "net/http" + "testing" +) + +func TestOperation(t *testing.T) { + + const ( + genesisAddr = "GDIRF4UWPACXPPI4GW7CMTACTCNDIKJEHZK44RITZB4TD3YUM6CCVNGJ" + genesisSecret = "SBECGI3FSCYHNQIMANNCWQSVA6S5C6L4BXFKAPMBAMI5V47NWXNE37MN" + + account1Addr = "GASB5RUQZB2VHM7Z25UEM374I655CUJK2OXFTNW275BZO5GEJULLSOIS" + account1Secret = "SBXVOPDRVGUWDRUDKWSH4PGBAHMC7FTPFQQKZ5MLBYQWROVNY73N3YIX" + account2Addr = "GAATM6UE2OJEISHYOPWPGU3BXZY766MEZR7VKLD65DF4UHDEDLIGDCXB" + //account2Secret = "SAUR6KPJY6GT7FQYDLYNXHHLIVPNA4JSJ4IABNBVYUBLZD7LCZM5KQPY" + ) + + c := client.NewClient("https://127.0.0.1:2830") + headers := http.Header{} + headers.Set("Content-Type", "application/json") + + //Create from genesis to Account 1 + { + const ( + createBalance = 10000000000 + account1To2Balance = createBalance / 10 + ) + createAccount(t, genesisAddr, genesisSecret, account1Addr, createBalance) + createAccount(t, account1Addr, account1Secret, account2Addr, account1To2Balance) + } + + { + opage, err := c.LoadOperationsByAccount(account1Addr) + require.NoError(t, err) + for _, op := range opage.Embedded.Records { + if op.Source == account1Addr { + require.Equal(t, op.Target, account2Addr) + } + if op.Target == account1Addr { + require.Equal(t, op.Source, genesisAddr) + } + } + } + + { + opage, err := c.LoadOperationsByAccount(account2Addr) + require.NoError(t, err) + for _, op := range opage.Embedded.Records { + if op.Target == account2Addr { + require.Equal(t, op.Source, account1Addr) + } + } + } +}