Skip to content

Commit ebeff97

Browse files
authoredAug 9, 2023
Merge pull request #59 from everFinance/feature/getItems
add GetBundleItems(bundleInId string, itemsIds []string)
2 parents 9895c71 + 2059451 commit ebeff97

File tree

5 files changed

+190
-1
lines changed

5 files changed

+190
-1
lines changed
 

‎.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
main.go
22
go.sum
33
test-keyfile.json
4-
.DS_Store
4+
.DS_Store
5+
.idea
6+
vendor

‎client.go

+90
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/tidwall/gjson"
1111
"gopkg.in/h2non/gentleman.v2"
1212
"io/ioutil"
13+
"math"
1314
"math/big"
1415
"net/http"
1516
"net/url"
@@ -1052,3 +1053,92 @@ func (c *Client) SubmitToWarp(tx *types.Transaction) ([]byte, error) {
10521053
defer resp.Body.Close()
10531054
return ioutil.ReadAll(resp.Body)
10541055
}
1056+
1057+
/**
1058+
1059+
bundleBinary Data Format
1060+
+------------------+-----------------------------+--------------------------------------------+
1061+
| Number of Items | Headers (64 bytes) | Items' Binary Data |
1062+
| 32 bytes | 32 bytes (length) + 32 bytes | |
1063+
| | (ID) for each item | |
1064+
+------------------+-----------------------------+--------------------------------------------+
1065+
*/
1066+
1067+
func (c *Client) GetBundleItems(bundleInId string, itemsIds []string) (items []*types.BundleItem, err error) {
1068+
offset, err := c.getTransactionOffset(bundleInId)
1069+
if err != nil {
1070+
return nil, err
1071+
}
1072+
1073+
size, err := strconv.ParseInt(offset.Size, 10, 64)
1074+
if err != nil {
1075+
return nil, err
1076+
}
1077+
endOffset, err := strconv.ParseInt(offset.Offset, 10, 64)
1078+
startOffset := endOffset - size + 1
1079+
1080+
firstChunk, err := c.getChunkData(startOffset)
1081+
if err != nil {
1082+
return nil, err
1083+
}
1084+
1085+
itemsNum := utils.ByteArrayToLong(firstChunk[:32])
1086+
1087+
// get Headers endOffset
1088+
bundleItemStart := 32 + itemsNum*64
1089+
var containHeadersChunks []byte
1090+
if len(firstChunk) < bundleItemStart {
1091+
// To calculate headers, you need to pull several chunks and fetch an integer upwards
1092+
chunkNum := int(math.Ceil(float64(bundleItemStart) / float64(types.MAX_CHUNK_SIZE)))
1093+
1094+
for i := 0; i < chunkNum; i++ {
1095+
chunk, err := c.getChunkData(startOffset + int64(i*types.MAX_CHUNK_SIZE))
1096+
if err != nil {
1097+
return nil, err
1098+
}
1099+
containHeadersChunks = append(containHeadersChunks, chunk...)
1100+
}
1101+
} else {
1102+
containHeadersChunks = firstChunk
1103+
}
1104+
1105+
for i := 0; i < itemsNum; i++ {
1106+
headerBegin := 32 + i*64
1107+
end := headerBegin + 64
1108+
1109+
headerByte := containHeadersChunks[headerBegin:end]
1110+
itemBinaryLength := utils.ByteArrayToLong(headerByte[:32])
1111+
id := utils.Base64Encode(headerByte[32:64])
1112+
1113+
// if item is in itemsIds
1114+
if utils.ContainsInSlice(itemsIds, id) {
1115+
1116+
startChunkNum := bundleItemStart / types.MAX_CHUNK_SIZE
1117+
startChunkOffset := bundleItemStart % types.MAX_CHUNK_SIZE
1118+
data := make([]byte, 0, itemBinaryLength)
1119+
1120+
for offset := startOffset + int64(startChunkNum*types.MAX_CHUNK_SIZE); offset <= startOffset+int64(bundleItemStart+itemBinaryLength); {
1121+
chunk, err := c.getChunkData(offset)
1122+
1123+
if err != nil {
1124+
return nil, err
1125+
}
1126+
data = append(data, chunk...)
1127+
offset += int64(len(chunk))
1128+
}
1129+
1130+
itemData := data[startChunkOffset : startChunkOffset+itemBinaryLength]
1131+
item, err := utils.DecodeBundleItem(itemData)
1132+
if err != nil {
1133+
return nil, err
1134+
}
1135+
1136+
items = append(items, item)
1137+
}
1138+
// next itemBy start offset
1139+
bundleItemStart += itemBinaryLength
1140+
}
1141+
1142+
return items, nil
1143+
1144+
}

‎client_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,49 @@ func TestNewTempConn2(t *testing.T) {
348348
err = utils.VerifyBundleItem(*item)
349349
assert.NoError(t, err)
350350
}
351+
352+
// https://arweave.net/tx/x-q8ibbTfXIcdDXqQ3xaPD3PuShj832G_xzNT5QrVjY/offset
353+
// {"size":"753","offset":"146739359163367"}
354+
func Test_getChunkData(t *testing.T) {
355+
c := NewClient("https://arweave.net")
356+
data, err := c.getChunkData(146739359163367)
357+
assert.NoError(t, err)
358+
359+
t.Log(string(data))
360+
361+
}
362+
363+
func TestClient_GetBundleItems(t *testing.T) {
364+
c := NewClient("https://arweave.net")
365+
itemsIds := []string{"UCTEOaljmuutGJId-ktPY_q_Gbal8tyJuLfyR6BeaGw"}
366+
items, err := c.GetBundleItems("47KozLIAfVMKdxq1q3D1xFZmRpkahOOBQ8boOjSydnQ", itemsIds)
367+
assert.NoError(t, err)
368+
assert.Equal(t, 1, len(items))
369+
assert.Equal(t, "UCTEOaljmuutGJId-ktPY_q_Gbal8tyJuLfyR6BeaGw", items[0].Id)
370+
}
371+
372+
func TestClient_GetBundleItems2(t *testing.T) {
373+
c := NewClient("https://arweave.net")
374+
itemsIds := []string{"UCTEOaljmuutGJId-ktPY_q_Gbal8tyJuLfyR6BeaGw", "FCUfgEEPmZB3YQMTfbwYl6VA-JT54zLr5PrcJw2EFeM", "zlU0o99c81n0CP64F31ANpyJeOtlz5DKvsKohmbMxqU"}
375+
items, err := c.GetBundleItems("47KozLIAfVMKdxq1q3D1xFZmRpkahOOBQ8boOjSydnQ", itemsIds)
376+
377+
assert.NoError(t, err)
378+
assert.Equal(t, 3, len(items))
379+
assert.Equal(t, "UCTEOaljmuutGJId-ktPY_q_Gbal8tyJuLfyR6BeaGw", items[2].Id)
380+
assert.Equal(t, "FCUfgEEPmZB3YQMTfbwYl6VA-JT54zLr5PrcJw2EFeM", items[1].Id)
381+
assert.Equal(t, "zlU0o99c81n0CP64F31ANpyJeOtlz5DKvsKohmbMxqU", items[0].Id)
382+
}
383+
384+
// https://viewblock.io/zh-CN/arweave/tx/PRBVxEX00aVMN59EY8gznt83FTlGXZvESwv1WTP7ReQ 5000 items
385+
func TestClient_GetBundleItems3(t *testing.T) {
386+
c := NewClient("https://arweave.net")
387+
itemsIds := []string{"QD0ryQTy4CBr7kluWRLT1strRcXWJOgUUoIYat4lk1s", "BzsIVzo6rPfGQg0PP-5Y_HErPey51_it0d6aGIUfQnY", "fy3aOYoRf7OzCEd9_WrD-RfqbzNZ1LsJ4PKIIGUALik"}
388+
items, err := c.GetBundleItems("PRBVxEX00aVMN59EY8gznt83FTlGXZvESwv1WTP7ReQ", itemsIds)
389+
390+
assert.NoError(t, err)
391+
assert.Equal(t, 3, len(items))
392+
assert.Equal(t, "QD0ryQTy4CBr7kluWRLT1strRcXWJOgUUoIYat4lk1s", items[0].Id)
393+
assert.Equal(t, "fy3aOYoRf7OzCEd9_WrD-RfqbzNZ1LsJ4PKIIGUALik", items[1].Id)
394+
assert.Equal(t, "BzsIVzo6rPfGQg0PP-5Y_HErPey51_it0d6aGIUfQnY", items[2].Id)
395+
396+
}

‎utils/slice.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package utils
2+
3+
// ContainsInSlice checks if a string is in a slice of strings
4+
func ContainsInSlice(items []string, item string) bool {
5+
for _, eachItem := range items {
6+
if eachItem == item {
7+
return true
8+
}
9+
}
10+
return false
11+
}

‎utils/slice_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package utils
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestContainsInSlice(t *testing.T) {
9+
type args struct {
10+
items []string
11+
item string
12+
}
13+
tests := []struct {
14+
name string
15+
args args
16+
want bool
17+
}{
18+
{
19+
name: "string in slice",
20+
args: args{
21+
items: []string{"a", "b", "c"},
22+
item: "a",
23+
},
24+
want: true,
25+
},
26+
{
27+
name: "string not in slice",
28+
args: args{
29+
items: []string{"a", "b", "c"},
30+
item: "x",
31+
},
32+
want: false,
33+
},
34+
}
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
assert.Equalf(t, tt.want, ContainsInSlice(tt.args.items, tt.args.item), "ContainsInSlice(%v, %v)", tt.args.items, tt.args.item)
38+
})
39+
}
40+
}

0 commit comments

Comments
 (0)