-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DuneAPI: new batch wire format (breaking change!)
This is a breaking change, it changes the block batch format on the wire. It introduces a single JSON line (NDJSON) which is the batch header. This can be DISABLED with `DUNEAPI_DISABLE_BATCH_HEADER` environment variable This line indicates how many blocks and the size of each block message In essense, we're length prefixing the batch. This allows the reader to split out easily all the messages. Until now this wasn't needed because Optimism Stack (EVM stack) uses a fixed 3-line payload for the blocks. But this isn't the case in other EVM stacks (such as arbitrum nitro) It uses JSON to encode this information because the jsonRPC message from the blockchain RPC node are already in JSON and the wire format is already NDJSON. For the server side to handle in a backwards compatible way the payload, it needs to read the first line and attempt to parse it as a BlockBatchHeader message If that fails, it can revert back to the older message format parser
- Loading branch information
Showing
6 changed files
with
156 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package duneapi | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
|
||
"github.com/duneanalytics/blockchain-ingester/models" | ||
) | ||
|
||
type BlockBatchHeader struct { | ||
BlockSizes []int `json:"block_sizes"` | ||
} | ||
|
||
func WriteBlockBatch(out io.Writer, payloads []models.RPCBlock, disableHeader bool) error { | ||
// we write a batch header (single line, NDJSON) with the size of each block payload and then concatenate the payloads | ||
header := BlockBatchHeader{ | ||
BlockSizes: make([]int, len(payloads)), | ||
} | ||
for i, block := range payloads { | ||
header.BlockSizes[i] = len(block.Payload) | ||
} | ||
// allow disabling the header for testing/backwards compatibility | ||
if !disableHeader { | ||
buf, err := json.Marshal(header) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = out.Write(buf) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = out.Write([]byte("\n")) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
for _, block := range payloads { | ||
_, err := out.Write(block.Payload) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package duneapi_test | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"encoding/json" | ||
"io" | ||
"testing" | ||
|
||
"github.com/duneanalytics/blockchain-ingester/client/duneapi" | ||
"github.com/duneanalytics/blockchain-ingester/models" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestWriteBlockBatch(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
payloads []models.RPCBlock | ||
expected string | ||
}{ | ||
{ | ||
name: "single payload", | ||
payloads: []models.RPCBlock{ | ||
{Payload: []byte(`{"block":1}`)}, | ||
}, | ||
expected: `{"block_sizes":[11]} | ||
{"block":1}`, | ||
}, | ||
{ | ||
name: "multiple payloads, with new lines", | ||
payloads: []models.RPCBlock{ | ||
{Payload: []byte(`{"block":1}` + "\n")}, | ||
{Payload: []byte(`{"block":2}` + "\n")}, | ||
}, | ||
expected: `{"block_sizes":[12,12]} | ||
{"block":1} | ||
{"block":2} | ||
`, | ||
}, | ||
{ | ||
name: "multiple payloads, no newlines", | ||
payloads: []models.RPCBlock{ | ||
{Payload: []byte(`{"block":1}`)}, | ||
{Payload: []byte(`{"block":2}`)}, | ||
}, | ||
expected: `{"block_sizes":[11,11]} | ||
{"block":1}{"block":2}`, | ||
}, | ||
{ | ||
name: "empty payloads", | ||
payloads: []models.RPCBlock{}, | ||
expected: `{"block_sizes":[]} | ||
`, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
var buf bytes.Buffer | ||
err := duneapi.WriteBlockBatch(&buf, tt.payloads, false) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, tt.expected, buf.String()) | ||
rebuilt, err := ReadBlockBatch(&buf) | ||
require.NoError(t, err) | ||
require.EqualValues(t, tt.payloads, rebuilt) | ||
}) | ||
} | ||
} | ||
|
||
func ReadBlockBatch(buf *bytes.Buffer) ([]models.RPCBlock, error) { | ||
reader := bufio.NewReader(buf) | ||
headerLine, err := reader.ReadString('\n') | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var header duneapi.BlockBatchHeader | ||
err = json.Unmarshal([]byte(headerLine), &header) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
payloads := make([]models.RPCBlock, len(header.BlockSizes)) | ||
for i, size := range header.BlockSizes { | ||
payload := make([]byte, size) | ||
_, err := io.ReadFull(reader, payload) | ||
if err != nil { | ||
return nil, err | ||
} | ||
payloads[i] = models.RPCBlock{Payload: payload} | ||
} | ||
|
||
return payloads, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters